-
Notifications
You must be signed in to change notification settings - Fork 274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[译] 基于 Webpack 和 ES6 打造 JavaScript 类库 #56
Comments
(占楼备用) |
(@ο@) 哇~ |
学习了,赞! |
“library”译为“库”或“代码库”可能比“类库”更准确些,因为传统上js库可能并不以“类”的形式导出。 |
@hax |
👍 |
Nice |
Good |
试了一下,我发现最终出来的结果虽然是umd的,但是比如在浏览器上访问的时候,本来预期Wind应该是一个类,但是Wind.default才是,不知是否是要设置什么属性么? |
@icepy |
@leozdgao 我早就试过了,跟我预期的结果不一样。 let Wind = function(){
alert(123)
}
export default Wind 在浏览器中预期的结果是 期望的结果: window.Wind() // alert 123 实际的结果: window.Wind() //Uncaught TypeError: Wine is not a function 在控制台中,看了下 {
default:function Wind(){}
} 所以访问时需要:window.Wind.default() // alert 123 成功。 所以我才觉得这样不应该,想来询问是否是少了某个属性?webpack 打包的 |
好吧,我应该明白你的意思了,之前是我会错意了。 由于之前遇到过类似的坑,不清楚有没有改变 Babel 编译结果的可能,我目前统一使用 commonjs 的模块导出语法。由于我清楚 |
@icepy 主要问题在于 ES6 和 CommonJS 的模块特性并不是完全对应的,在相互转换的过程中难免出现一些信息丢失或错位。如果要给浏览器用,则需要经历 ES6 → CommonJS → UMD 这样的转换和包装过程,你在 ES6 源码中的意图无法准确传达到浏览器环境。
你可能是用 ES6 的默认导出方式( 如果讨厌 如果觉得后者还是不理想,则只有用 CommonJS 模块来导出你的类( 在最后一种方法中,虽然你写的是 CommonJS 模块,但你在这个模块内仍然可以使用 ES6 语法(包括 class 等)。只不过在这种情况下 Babel 只需要编译 ES6 语法,不需要转换模块格式。 |
你看一下 Babel 对 ES6 模块的转译结果,以及 UMD 对 CommonJS 模块的包装原理,应该就明白了。 |
@cssmagic 是换成了CommonJS的语法,没用export和default,才和我预期的结果一致,我以为Babel有什么属性可以设置,直接转译成 |
@leozdgao 我和你的思路一样,也是改成了commonjs的语法。 |
@icepy 前段时间刚在微博上讨论过 babel 从 ES6 module 转到 CommonJS 模块的话题,在这里刚好复制一下,可以解答你的疑问
|
@sodatea |
babel-plugin-add-module-exports 好物 👍 |
@sodatea 感谢分享 👍 |
@sodatea 感谢分享 ^_^ |
🌹 感谢~ |
找了好久,终于看到这么清晰明了的配置了 |
现在我有一个依赖 jQuery 的库: import $ from 'jquery'
... 我在作者原文下找到 krasimir/webpack-library-starter#3 ,所以我这样写了 externals: externals: {
'jquery': 'jQuery'
} 这样的话,我的库就很小了。但是,当我使用我的库并且用 webpack 打包的时候,发现我的库和jquery打包在了一起。 这种情况怎么解? |
简单说一下我的猜测:使用你的库的时候(此时你的库已经不再是主项目,而是和 jQuery 一样是依赖),Webpack 的配置同样需要加上这段 |
感谢。 |
赞一个 |
感谢,最近在找这个打包功能~ |
非感谢译者,刚好在找这方面的文章 |
謝謝大佬啊,找了一個下午的資料,看到這個文章太棒了真的! |
[译] 基于 Webpack 和 ES6 打造 JavaScript 类库
Two months ago I published a starter pack for React based on webpack. Today I found out that I need almost the same thing but without the React bit. This simplifies the setup but there are still some tricky parts. So, I made a brand new repository webpack-library-starter and placed all the stuff that we need for creating a JavaScript library.
两个月前,我曾发布了一篇基于 webpack 的 React 起步教程。你眼前的这篇文章跟那一篇差不多,只不过不包含 React 那一块。这篇教程稍微简单一些,但仍然会有一些棘手的部分。因此,我特意建了一个全新的代码仓库 webpack-library-starter,把创建一个 JavaScript 类库所需的所有素材都放了进去。
First of all, what I meant by saying “library”
首先,我们说的 “类库” 是指什么
My definition for library in the context of JavaScript is a piece of code that provides specific functionality. It does one thing and it is doing it well. In the ideal case should not depend on another library or framework. A good example for library is jQuery. React and Vue.js could be also considered a library.
在 JavaScript 语境中,我对类库的定义是 “提供了特定功能的一段代段”。一个类库只做一件事,并且把这件事做好。在理想情况下,它不依赖其它类库或框架。jQuery 就是一个很好的例子。React 或者 Vue.js 也可以认为是一个类库。
The library should:
一个类库应该:
<script>
tag.<script>
标签来引入这个类库。It doesn’t matter what is used for developing the library. What is important is the file that is distributed. It should match the above requirements. I prefer to see libraries written in vanilla JavaScript though. It simply makes the contribution easier.
用什么来开发这个类库并不重要,重要的是我们最终产出的文件。它只要满足上述要求就行。尽管如此,我还是比较喜欢用原生 JavaScript 写成的类库,因为这样更方便其它人贡献代码。
Directory structure
目录结构
I choose the following directory structure:
我一般选择如下的目录结构:
Where
src
contains the source files andlib
the final compiled version. This means that the entry point of the library is the file underlib
and notsrc
.其中
src
目录用于存放源码文件,而lib
目录用于存放最终编译的结果。这意味着类库的入口文件应该放在lib
目录下,而不是src
目录下。The starter
起步动作
I really enjoy the new ES6 specification. The bad thing is that there is some significant tooling around it. Some day we’ll probably write such JavaScript without the need of transpiler but today that’s not the case. Usually we need some sort of Babel integration. Babel can convert our ES6 files to ES5 format but it is not meant to create bundles. Or in other words, if we have the following files:
我确实很喜欢最新的 ES6 规范。但坏消息是它身上绑了一堆的附加工序。也许将来某一天我们可以摆脱转译过程,所写即所得;但现在还不行。通常我们需要用到 Babel 来完成转译这件事。Babel 可以把我们的 ES6 文件转换为 ES5 格式,但它并不打算处理打包事宜。或者换句话说,如果我们有以下文件:
And we apply Babel we’ll get:
然后我们用上 Babel,那我们将会得到:
Or in other words Babel do not resolve the imports/requires. So we need a bundler and as you may guess my choice for that is webpack. What I want to achieve at the end is:
或者再换句话说,Babel 并不解析代码中的
import
或require
指令。因此,我们需要一个打包工具,而你应该已经猜到了,我的选择正是 webpack。最终我想达到的效果是这样的:npm commands
npm 命令
npm provides a nice mechanism for running tasks - scripts. There should be at least three of those registered:
在运行任务方面,npm 提供了一套不错的机制——scripts(脚本)。我们至少需要注册以下三个脚本:
npm run build
- this should produce a final minified version of our librarynpm run dev
- the same asbuild
but do not minify the result and keeps working in a watching modenpm run test
- runs the testsnpm run build
- 这个脚本用来生成这个类库的最终压缩版文件。npm run dev
- 跟build
类似,但它并不压缩代码;此外还需要启动一个监视进程。npm run test
- 用来运行测试。Building the development version
构建开发版本
npm run dev
should fire webpack and should producelib/library.js
file. We start from the webpack’s configuration file:npm run dev
需要调用 webpack 并生成lib/library.js
文件。我们从 webpack 的配置文件开始着手:Even if you don’t have experience with webpack you may say what is this config file doing. We define the input (
entry
) and the output (output
) of the compilation. Themodule
property says what should be applied against every file during processing. In our case this is babel and ESLint where ESLint is a used for checking the syntax and correctness of our code.即使你还没有使用 webpack 的经验,你或许也可以看明白这个配置文件做了些什么。我们定义了这个编译过程的输入(
entry
)和输出(output
)。那个module
属性指定了每个文件在处理过程中将被哪些模块处理。在我们的这个例子中,需要用到 Babel 和 ESLint,其中 ESLint 用来校验代码的语法和正确性。There is one tricky part where I spent couple of ours. It’s related to
library
,libraryTarget
andumdNamedDefine
properties. First I tried without using them and the output of the library was something like this:这里有一个坑,花了我不少的时间。这个坑是关于
library
、libraryTarget
和umdNamedDefine
属性的。最开始我没有把它们写到配置中,结果编译结果就成了下面这个样子:This is how every webpack compiled code looks like. It uses similar approach like browserify. There is a self-invoking function which receives all the modules used in our application. Every of them stays behind an index of the
modules
array. In the code above we have only one and__webpack_require__(0)
effectively runs the code in oursrc/index.js
file.经过 webpack 编译之后的文件差不多都是这个样子。它采用的方式跟 Browserify 很类似。编译结果是一个自调用的函数,它会接收应用程序中所用到的所有模块。每个模块都被存放到到
modules
数组中。上面这段代码只包含了一个模块,而__webpack_require__(0)
实际上相当于运行src/index.js
文件中的代码。Having a bundle like this one do not fulfill all the requirements mentioned in the beginning of this article because we do not export anything. The file is meant to be dropped in a web page. However, adding
library
,libraryTarget
andumdNamedDefine
makes webpack injecting a really nice snippet at the top:光是得到这样一个打包文件,并没有满足我们在文章开头所提到的所有需求,因为我们还没有导出任何东西。这个文件的运行结果在网页中必定会被丢弃。不过,如果我们加上
library
、libraryTarget
和umdNamedDefine
,就可以让 webpack 在文件顶部注入一小段非常漂亮的代码片断:Setting
libraryTarget
toumd
means using universal module definition for the final result. And indeed, this piece of code recognizes the environment and provides a proper bootstrapping mechanism for our library.把
libraryTarget
设定为umd
表示采用 通用模块定义 来生成最终结果。而且这段代码确实可以识别不同的运行环境,并为我们的类库提供一个妥当的初始化机制。Building production version
构建生产环境所需的版本
The only one difference between development and production mode for webpack is the minification. Running
npm run build
should produce a minified version -library.min.js
. webpack has a nice build-in plugin for that:对 webpack 来说,开发阶段与生产阶段之间唯一的区别在于压缩。运行
npm run build
应该生成一个压缩版——library.min.js
。webpack 有一个不错的内置插件可以做到这一点:UglifyJsPlugin
does the job if we add it to theplugins
array. There is something else that we have to clarify. We need some conditional logic where we instruct webpack what kind of bundle to produce (production or development). One of the popular approaches is to define an environment variable and pass it from the command line. For example:只要我们把
UglifyJsPlugin
加入到plugins
数组中,它就可以完成这个任务。此外,还一些事情有待明确。我们还需要某种条件判断逻辑,来告诉 webpack 需要生成哪一种类型(“开发阶段” 还是 “生产阶段”)的打包文件。一个常见的做法是定义一个环境变量,并将它通过命令行传进去。比如这样:(Notice the
--watch
option. It makes webpack continuously running and watching for changes)(请留意
--watch
选项。它会让 webpack 监视文件变化并持续运行构建任务。)Testing
测试
I’m usually using Mocha and Chai for testing and that’s what I added in the starter. Again there was a tricky part making Mocha understands ES6 files but thankfully to Babel the problem was resolved.
我通常采用 Mocha 和 Chai 来运行测试——测试环节是这篇起步教程特有的内容。这里同样存在一个棘手的问题,就是如何让 Mocha 正确识别用 ES6 写的测试文件。不过谢天谢地,Babel 再次解决了这个问题。
The important bit is the
--compilers
option. It allows us to process the incoming file before running it.这里最关键的部分在于
--compilers
这个选项。它允许我们在运行测试文件之前预先处理这个文件。A few other configuration files
其它配置文件
Babel received some major changes in the newest version 6. We now have something called
presets
where we describe what kind of transformation we want. One of the easiest ways to configure that is with a.babelrc
file:在最新的 6.x 版本中,Babel 发生了一些重大的变化。现在,在指定哪些代码转换器将被启用时,我们需要面对一种叫作
presets
的东西。最简单配置的方法就是写一个.babelrc
文件:ESLint provides the same thing and we have
.eslintrc
:ESLint 也需要一个类似的配置文件,叫作
.eslintrc
:Links
相关链接
The starter is available in GitHub here github.com/krasimir/webpack-library-starter.
这篇起步教程还可以在 GitHub 上找到:github.com/krasimir/webpack-library-starter。
Used tools:
用到的项目如下:
Dependencies:
具体依赖如下:
译者后记
是不是意犹未尽?其实准确来说,这篇文章是作者对 webpack-library-starter 项目的一个简要解说,讲解了代码之外的背景知识。
因此,作为学习者,光读文章是远远不够的,我们真正需要的是研读这个项目提供的源码,并且动手实际操作和演练,如此方能掌握要领。加油!
© Creative Commons BY-NC-ND 4.0 | 我要订阅 | 我要打赏
The text was updated successfully, but these errors were encountered: