初学教程2部分- 使用Webpack来搭建ES6模块

在上一篇中我们已经学习了基础的webpack使用方式,在某些新项目中,我们总想试一试ES6,但是大部分浏览器是不支持的,我们这个时候要学会利用babel进行转换编译。

如果我们写过ES6,因为他的简捷和方便的用法,我们便不会再去想回去写ES5甚至ES3。如果你还没有机会使用ES6,很大一部分原因就是因为不了解如何构建开发环境和使用设定配置来帮助我们撰写WS6。

这些配置总是让初学者沮丧,包括我自己,所以我尽可能让这些过程看起来朴实易懂。

必要条件

  1. 如果你还没有看过第一部分,你应该确认自己对webpack基础部分的了解。

  2. 有关ES6的学习和描述,ECMAScript 6入门是个很好的资源,或者是es6-cheatsheet

目录

Babel

如果你想要有更深入的了解和更多详细,甚至可以说是繁杂的关于babel的手册,请跳转到Babel官网,在此我只做一些简单的描述。

Babel是做什么的

简单来说,babel让我们可以更完整地使用javascript的ES6特性,因为目前大部分的浏览器和环境都不支持ES6的绝大数新特性,所以我们利用babel将它转换成ES5,让我们的ES6写法的代码可以被广泛运行。

这个ES6的程式,目前只有最新的浏览器支持。

  1. const square = n => n * n;

它会被转换成大概这个样:

  1. var square = function(n){
  2. return n * n
  3. };

这样就能在普通的javascript环境运行了。

如何设定Babel

在项目的根目录下我们可以新建一个后缀名为.babelrc的文件,这里解释一下为什么是.babelrc呢?

熟悉linux的同学知道,rc结尾的文件通常代表运行时自动加载的文件,配置等等,同样的这里的babelrc会在webpack打包时运行babel访问这个配置文件,如果不想分离,可以把babelrc的设置放到package.json中也可。

  1. .babelrc

我们的babelrc里面的字符量非常少,

  1. {
  2. "presets": ["es2015", "stage-0"]
  3. }

这个”presets”代表了启动怎样的预设转码,es2015代表启动es6语法,而后面的stage则有stage-0、stage-1、stage-2,这里我一般使用stage-0,因为它代表了绝大多数的标准情况。
总之,如果要用到这些预设配置presets,我们就需要用npm安装并依赖他们:

  1. npm install --save-dev babel-preset-es2015 babel-preset-stage-0

Webpack

就此,我们可以使用part1部分相同的webpack配置js文件,但是需要加入ES6所需要的功能。

这是我们目前Webpack配置的javascript文件内容:

  1. // webpack.config.dev.js
  2. var path = require('path')
  3. var webpack = require('webpack')
  4. var HtmlWebpackPlugin = require('html-webpack-plugin')
  5. module.exports = {
  6. devtool: 'cheap-eval-source-map',
  7. entry: [
  8. 'webpack-dev-server/client?http://localhost:8080',
  9. 'webpack/hot/dev-server',
  10. './src/index'
  11. ],
  12. output: {
  13. path: path.join(__dirname, 'dist'),
  14. filename: 'bundle.js'
  15. },
  16. plugins: [
  17. new webpack.HotModuleReplacementPlugin(),
  18. new HtmlWebpackPlugin({
  19. template: './src/index.html'
  20. })
  21. ],
  22. module: {
  23. loaders: [{
  24. test: /\.css$/,
  25. loaders: ['style', 'css']
  26. }]
  27. },
  28. devServer: {
  29. contentBase: './dist',
  30. hot: true
  31. }
  32. }

  1. // webpack.config.prod.js
  2. var path = require('path')
  3. var webpack = require('webpack')
  4. var HtmlWebpackPlugin = require('html-webpack-plugin')
  5. module.exports = {
  6. devtool: 'source-map',
  7. entry: ['./src/index'],
  8. output: {
  9. path: path.join(__dirname, 'dist'),
  10. filename: 'bundle.js'
  11. },
  12. plugins: [
  13. new webpack.optimize.UglifyJsPlugin({
  14. compressor: {
  15. warnings: false,
  16. },
  17. }),
  18. new webpack.optimize.OccurrenceOrderPlugin(),
  19. new HtmlWebpackPlugin({
  20. template: './src/index.html'
  21. })
  22. ],
  23. module: {
  24. loaders: [{
  25. test: /\.css$/,
  26. loaders: ['style', 'css']
  27. }]
  28. }
  29. }

一个新的Loader

如果我们要将我们包含ES6的代码转换成ES5需要在配置文件中添加一个叫做babel-loader的新loader,他和babel-core有依赖关系。这个loader访问并读取了我们的.babelrc配置项设定的档案来按规则转换我们的代码。

  1. npm install --save-dev babel-loader babel-core

我们在dev和prod这两个配置文件中加入:

  1. // 为了节省篇幅只贴出`loaders`部分。
  2. // webpack.config.dev.js 和 webpack.config.prod.js
  3. module: {
  4. loaders: [{
  5. test: /\.css$/,
  6. loaders: ['style', 'css']
  7. }, {
  8. test: /\.js$/,
  9. loaders: ['babel'],
  10. include: path.join(__dirname, 'src')
  11. }]
  12. }

一件非常重要的事情,请注意include属性的用法。当我们运行webpack时,因为我们在test中设定了匹配.js结尾的文件,webpack将会在每一个文件夹的.js文件尝试去执行babel-loader

这样?你也能看到问题了吧,他会无线执行我们不需要编译的任何项目内js,包括node_modules,这样缺失的设置,会极端地延长我们的构建过程···so long so long。

include可以防止这个问题,loader只会在我们所指定的src目录下匹配.js文件。

其次,我们还可以这样干!把 include:path.join(__dirname,'src') 改成 exclude:/node_modules/,这样反排除我们不想要编译的文件夹。

我们完成了?

说实话,为了容易的初衷,我让这个教学过程显得有点长了,现在我们就来看看加入了babel之后,我们用ES6语法把我们之前的index.js重构成什么样了:

  1. // index.js
  2. // 接受 hot module reloading
  3. if (module.hot) {
  4. module.hot.accept()
  5. }
  6. require('./styles.css') // 網頁現在有了樣式
  7. const Please = require('pleasejs')
  8. const div = document.getElementById('color')
  9. const button = document.getElementById('button')
  10. const changeColor = () => div.style.backgroundColor = Please.make_color()
  11. button.addEventListener('click', changeColor)

引入ES6的模块

需要提醒的是,我们现在可以使用ES6的module系统。例如

  1. const Please = require('pleasejs')

我们可以改成import方式:

  1. import Please from 'pleasejs'

额外的补充

有两个其实很重要的主题我并没有太多的篇幅可以去覆盖它们:

Production环境参数

Webpack

如果我们不想要在production执行部分的代码,我们可以使用 DefinePlugin

这个plugin将会为我们的整个构建来创建一个全局常量,我们可以用任何名字来命名,比如

DONT_USE_IN_PRODUCTION: true,但是更多情况下,我们最好这样选择来使用:

process.env.NODE_ENV: JSON.stringify('production')

这是因为许多代码可以识别process.env.NODE_ENV,并使用额外的功能和优化。

为什么要使用JSON.stringify?,根据资料的解释process.env.NODE_ENV的值

If the value is a string it will be used as a code fragment.

这意味着'production'将会是一个错误,如果你不想使用'production''“production”'也是一个选择。

如此,我们的plugins数组会像这样:

  1. plugins: [
  2. new webpack.optimize.UglifyJsPlugin({
  3. compressor: {
  4. warnings: false,//压缩错误信息
  5. },
  6. }),
  7. new webpack.optimize.OccurrenceOrderPlugin(),//自动优化分配
  8. new HtmlWebpackPlugin({
  9. template: './src/index.html'//模版html
  10. }),
  11. new webpack.DefinePlugin({
  12. 'process.env.NODE_ENV': JSON.stringify('production')//设定环境变量
  13. })
  14. ]

现在,我怕们可以排除掉一些代码不在我们的生产环境中运行,我们可以写这样判断一条语句:

  1. if (process.env.NODE_ENV !== 'production') {
  2. // not for production
  3. }

在我们目前的案例中,如果在production环境下,我们可以排除掉hot reload:

  1. // 只在非生产环境中使用热重载
  2. if (process.env.NODE_ENV !== 'production') {
  3. if (module.hot) {
  4. module.hot.accept()
  5. }
  6. }

Babel

定义我们的 production 变量process.env.NODE_ENV有其它额外的好处。

根据手册:

目前的环境将会优先读取process.env.BABEL_ENV。当BABEL_ENV没有定义时,它将会降级使用NODE_ENV,再如果它也没有被定义的话,默认替代使用为”development”。

它意味着babel环境会匹配我们的webpack环境。

我们能够增加这个预设在我们的.babelrc文件中,添加我们的development时也就是开发环境特有的配置:

  1. {
  2. "presets": ["es2015", "stage-2"],
  3. "env": {
  4. // 只发发生在 NODE_ENV没有被定义或者是被定义为"development"
  5. "development": {
  6. // 当NODE_ENV是production时不运行
  7. }
  8. }

我们会在part3和react的部分重新介绍这一块 关于 React Transform HMR

加入Lint

如果你看过其他关于Webpack/React案例的教程or实例,你应该是看过一个同样rc后缀的文件——.eslintrc,如果你使用的不是IDE编辑器,而是文本编辑器类似于Sublime之类的,eslint可以提供语法和风格的检查,并检查出我们的语法以及风格上的错误和注意警告。另外,就算我们正在使用IDE,它也能提供更多的功能,来确保整个项目的代码风格统一。

请注意,如果我们想要在编辑器内使用它,需要安装插件,在此我不做赘述。

简单描述一些安装和配置:

  1. npm install -g eslint
  2. npm install --save-dev eslint eslint-config-airbnb

我们的.eslintrc

  1. // .eslintrc
  2. {
  3. "extends": "airbnb/base" // 'airbnb/base' 因為 'airbnb' 假設使用 react
  4. }

我们可以稍微调整这些规则,来适应我们的喜好,比如:

  1. // .eslintrc
  2. {
  3. "extends": "airbnb/base",
  4. "rules": {
  5. "comma-dangle": 0,
  6. "no-console": 0,
  7. "semi": [2, "never"],
  8. "func-names": 0,
  9. "space-before-function-paren": 0,
  10. "no-multi-spaces": 0
  11. }
  12. }

另外,eslint内置不支持babel语法,为此,我们需要安装两个plugin:

  1. npm install --save-dev babel-eslint eslint-plugin-babel

再一次调整我们的.eslintrc配置文件,加入我们指定的babel规则:

  1. // .eslintrc
  2. {
  3. "extends": "airbnb/base",
  4. "parser": "babel-eslint",
  5. "rules": {
  6. "comma-dangle": 0,
  7. "no-console": 0,
  8. "semi": [2, "never"],
  9. "func-names": 0,
  10. "space-before-function-paren": 0,
  11. "no-multi-spaces": 0,
  12. "babel/generator-star-spacing": 1,
  13. "babel/new-cap": 1,
  14. "babel/object-shorthand": 1,
  15. "babel/arrow-parens": 1,
  16. "babel/no-await-in-loop": 1
  17. },
  18. "plugins": [
  19. "babel"
  20. ]
  21. }

最后,在我们的package.json中加入lint会是个好主意。

  1. // package.json
  2. "scripts": {
  3. "build": "webpack --config webpack.config.prod.js",
  4. "dev": "webpack-dev-server --config webpack.config.dev.js",
  5. "lint": "eslint src"
  6. }

我们可以随时运行 npm run lint 来确保我们的代码没有违反指定的规则。

其实说老实话,这个关于lint的配置具有不匹配个人风格价值的繁琐 :anguished: ,除非我们是团队工作或者是用惯了文本编辑器,才会十分有必要去使用它。

结论

我把这一切的最终配置放进 demo1 .

现在我们可以轻松地撰写ES6特性的javascript代码,我们同时更加了解了webpack,当es6的代码跑起在浏览器中的时候还真是激动。

将来可能的部分:

如果有误或者更好的建议,请在issue提出,我们来一起讨论。

感谢你的阅读哟。。