现在我们已经学习了基础的 webpack 使用方式,为了撰写 ES6,我们要学会利用 babel 6,因为 ES6 是 JavaScript 新的规范。
如果你曾经撰写过 ES6,应该就不想再回去写 ES5 了。如果你还没有机会撰写 ES6,很大的原因可能是因为不了解开发环境该使用哪些配置选项,因为那些配置很令人沮丧。
我希望这个教学课程可以让这些过程可以变得更容易。
- 如果你还没有准备好,请先阅读 part 1
- 有关 ES6 的概述,es6-cheatsheet 是一个很棒的资源。
我很乐意接受任何所有的贡献或是修正。如果你有任何问题,可以将这些问题发成 issue。如果我有错误的话,请将问题指出。最后,如果你觉得我漏了些什么,或者可以将某些部分解释的更好,留下一个 issue 或者是发送 Pull Request。
如果你想要有更深入的说明,和更细微的配置 babel,请参考这个手册。我在这里说明的是一些基本的配置。
简单来说,babel 让你可以更完整的使用 JavaScript 的 ES6 feature,因为目前大部分的浏览器和环境都不支持,所以将它转换成 ES5,让它可以更广泛的被支持。
这个 ES6 的代码,目前只有最新的浏览器才支持。
const square = n => n * n;
它会被转成像是:
var square = function square(n) {
return n * n;
};
让你可以执行在任何支持 JavaScript 的地方。
另一个工具、另一个配置文件。这个时候我们有个文件叫做:
.babelrc
感谢啊!.babelrc
文件只有一行代码而已。
{
"presets": ["es2015", "stage-2"]
}
你只需要指定一个 presets
选项,下面是描述的摘录:
JavaScript 也有一些可能成为标准的提案,正在 TC39(ECMAScript 标准背后的委员会)的程序中。
这个程序被分为 5 个 statge(0-4)。如果提案获得更多的同意,通过各个 stage,就很容易被接受纳入标准中,最后在 stage 4 中被接受纳入标准。
注意,这里没有 stage-4 的 preset,它只是作为的
es2015
的 preset。 以上。
总结以上,presets
就是一些打包了 plugins
的 bundles,它们将一些功能加入到你在撰写的代码。es2015
中的功能,肯定会出现在 ES6 的官方版本,而 stages 0-3 的 presets ,则是未来 JavaScript 规范的一些提案,现在还在草案阶段。如果选择的 stage 越低,你使用的 features 之后将不支持的风险越高。
从我的经验来说,我至少需要 stage-2
,让我可以使用一个叫作 object spread 的东西。你可以在这里看看其他的提案,然后决定你要使用哪个 stage。
总之,如果要使用到这些 presets,我们需要安装它们:
npm install --save-dev babel-preset-es2015 babel-preset-stage-2
而实际上你全部需要做的事情就只有这个。
我们可以使用与 part 1-范例七相同的配置,但是需要加入 ES6 所需要的功能。
目前配置:
// webpack.config.dev.js
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'cheap-eval-source-map',
entry: [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/dev-server',
'./src/index'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
module: {
loaders: [{
test: /\.css$/,
loaders: ['style', 'css']
}]
},
devServer: {
contentBase: './dist',
hot: true
}
}
和
// webpack.config.prod.js
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'source-map',
entry: ['./src/index'],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
},
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
module: {
loaders: [{
test: /\.css$/,
loaders: ['style', 'css']
}]
}
}
如果要将我们的代码转换成 ES5,我们需要通过执行一个新的 loader 叫作 babel-loader
,它和 babel-core
有依赖关係。这个 loader 使用了我们的 .babelrc
配置来了解和转换我们的代码。
npm install --save-dev babel-loader babel-core
我们在 dev 和 prod 这两个配置中加入:
// 为了节省篇幅我只显示「loaders」的部分。
// webpack.config.dev.js 和 webpack.config.prod.js
module: {
loaders: [{
test: /\.css$/,
loaders: ['style', 'css']
}, {
test: /\.js$/,
loaders: ['babel'],
include: path.join(__dirname, 'src')
}]
}
一件非常重要的事情,请注意 include
属性的用法。当我们执行 webpack
时,因为我们在 test
有配置 /.js$/
,webpack 会在你的 dependency tree 每一个 js
文件尝试执行 babel loader。
你可以看出这有什么问题吗?要是我 require('bluebird')
,或是任何其他大型的 npm
package 会怎样?它会藉由 babel-loader
尝试执行整个 node_modules,这样大量的执行会延长你的 build 过程。
include
可以防止这个这个问题,loader 只会套用在你所指定 src
目录下的 .js
文件。
另一个方式是,你可以将 include: path.join(__dirname, 'src')
改变成 exclude: /node_modules/
,这意思是除了 node_modules
目录外其他都包括。更多信息可以在这里找到。
老实说,我以为这个教学会更长,但看起来我忘记了「加入 babel」这件事实际上非常简单。现在我们可以使用 ES6 语法来更新先前 index.js
的代码了:
// index.js
// 接受 hot module reloading
if (module.hot) {
module.hot.accept()
}
require('./styles.css') // 网页现在有了样式
const Please = require('pleasejs')
const div = document.getElementById('color')
const button = document.getElementById('button')
const changeColor = () => div.style.backgroundColor = Please.make_color()
button.addEventListener('click', changeColor)
另一件事情,注意到我们现在可以使用 ES6 的 module 系统。例如:
const Please = require('pleasejs')
我们可以修改成:
import Please from 'pleasejs'
既然前面没花太多时间,我将再讨论两个很重要且有用的主题。
如果我们不想要在 production 执行部分的代码,我们可以使用方便的 DefinePlugin。
这个 plugin 让我们可以为我们整个 bundle 建立全域的常数,我们可以命名任何常数,像是:DONT_USE_IN_PRODUCTION: true
,但是大多普遍的方式会是 process.env.NODE_ENV: JSON.stringify('production')
,这会是更好的选择。这是因为许多程序可以识别并根据 process.env.NODE_ENV
来使用额外的功能和优化你的代码。
为什么要 JSON.stringify
?因为根据文件的解释:
如果值是一个字串,它会被作为一个代码片段。
这意思说一个 'production'
只会是一个错误。如果你认为 JSON.stringify
很奇怪,一个有效替代方式是 '"production"'
。
你的 plugin 阵列现在看起来应该像:
plugins: [
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
},
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
现在,假如我们不想要在 production 上执行一些代码,我们可以放入一个 if 条件式:
if (process.env.NODE_ENV !== 'production') {
// not for production
}
在我们目前的专案中,如果在 production 环境下,我们可以排除 hot reload:
// 在开发环境下接受 hot module reloading
if (process.env.NODE_ENV !== 'production') {
if (module.hot) {
module.hot.accept()
}
}
将我们的 production 变量定义为 process.env.NODE_ENV
有其他额外的好处。
「目前环境」是使用 process.env.BABEL_ENV。当找不到 BABEL_ENV 时, 它会退回去找 NODE_ENV,如果也找不到 NODE_ENV,目前环境将设为预设值 "development"。
这个意思说 babel 环境会 match 到我们的 webpack 环境。
我们可以利用这一点,只要通过在我们的 .babelrc
加入 env
配置,就可以使用开发环境:
{
"presets": ["es2015", "stage-2"],
"env": {
// 只发生在 NODE_ENV 没有被定义或是被配置为 'development'
"development": {
// 当 NODE_ENV 是 production 忽略!
}
}
当我们介绍 React Transform HMR,我们将使用这个在 part 3 和 react。
如果你看过任何关于 Webpack/React 专案的 seed/starter,你可能看过一个文件叫做 .eslintrc
。如果你不是使用 IDE,而是使用像是 Atom、Sublime、Ecmas、Vim 等等,eslint 提供语法和风格的检查,指出你的错误。此外,即使你正在使用 IDE,它可以提供更多功能,并确保整个专案代码风格统一。
请注意,如果你相要在编辑器内使用它,你需要安装一个套件,例如我使用 Atom linter-eslint。
如果要减少我们手动撰写配置,我们可以充分的利用继承,使用他人的配置。我喜欢使用基于 airbnb 的风格指南配置。
开始之前,我们须安装 eslint 和 airbnb 的配置:
npm install -g eslint
npm install --save-dev eslint eslint-config-airbnb
我们的配置看起来像:
// .eslintrc
{
"extends": "airbnb/base" //使用 'airbnb/base' ,因为 'airbnb' 是假设使用 react
}
然而,因为 linting 是非常固执的,我喜欢将它调整一些。如果你想要知道这些规则的含意,或是根据你的喜好调整他们,可以查看这里:
// .eslintrc
{
"extends": "airbnb/base",
"rules": {
"comma-dangle": 0,
"no-console": 0,
"semi": [2, "never"],
"func-names": 0,
"space-before-function-paren": 0,
"no-multi-spaces": 0
}
}
另外,eslint 内建不支持和分辨 babel 语法,所以我们需要安装两个套件:
npm install --save-dev babel-eslint eslint-plugin-babel
调整我们的配置,再一次的加入 babel 指定规则:
// .eslintrc
{
"extends": "airbnb/base",
"parser": "babel-eslint",
"rules": {
"comma-dangle": 0,
"no-console": 0,
"semi": [2, "never"],
"func-names": 0,
"space-before-function-paren": 0,
"no-multi-spaces": 0,
"babel/generator-star-spacing": 1,
"babel/new-cap": 1,
"babel/object-shorthand": 1,
"babel/arrow-parens": 1,
"babel/no-await-in-loop": 1
},
"plugins": [
"babel"
]
}
最后,在我们的 package.json 文件中的 scripts 加入 lint 会是个好主意。
// package.json
"scripts": {
"build": "webpack --config webpack.config.prod.js",
"dev": "webpack-dev-server --config webpack.config.dev.js",
"lint": "eslint src"
}
当你执行 npm run lint
来确保你的代码没有违反你指定的规则。
我已经把所有这一切的最终结果放入到范例一。如果你仍然有不理解的地方,可以在 issue 提出你的问题。
所以现在我们可以轻鬆的撰写 ES6 代码,此外,也让我们了解到如何撰写配置 🎉!
然而,你有能力从头开始撰写它,并不表示你一定要这么做。为了方便,我有建立一个 repository 让你 clone 下来开始,这是根据这份教学建立的基本文件。
对未来的期望:
- Part 3 将会加入 React
- Part 4 将会涵盖更多进阶的 webpack 功能
感谢你的阅读!