今天动动手回顾下 Webpack 基本配置,搭了个 react 项目脚手架,做下笔记。
本文适合有了解过 webpack 基础配置的人阅读,因为有些基础配置和流程我就不做详细解释了。
初始化
新建文件夹react-cli,初始化
1
   | yarn add webpack webpack-cli -D
   | 
我们知道 webpack 主要是帮我们打包构建,我们新建个文件src/index.js
1
   | console.log('hello webpack')
  | 
1 2 3 4 5 6 7 8 9
   | const path = require('path')
  module.exports = {   entry: './src/index.js',   output: {     path: path.resolve(__dirname, 'dist'),     filename: 'app.js',   }, }
  | 
再到package.json设置打包命令
1 2 3
   | "scripts": {   "build": "npx webpack --config webpack.config.js" },
  | 
因为我没有全局安装 webpack,这里使用 npx(npm5.2 开始自带 npx 命令)
关于 npx 的使用可以看阮一峰大神这篇博客:npx 使用教程
就会看到生成了dist文件夹和打包生成的文件app.js
引入 Html 模板
html-webpack-plugin简化了 HTML 文件的创建,该插件将为你生成一个 HTML5 文件, 其中包括使用 script 标签的 body 中的所有 webpack 包。
1
   | yarn add html-webpack-plugin -D
   | 
新建模板 HTML 文件public/index.html
1 2 3 4 5 6 7 8 9 10 11
   | <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF-8" />     <meta name="viewport" content="width=device-width, initial-scale=1.0" />     <title>Document</title>   </head>   <body>     <div id="root">Hello</div>   </body> </html>
   | 
在webpack.config.js配置
1 2 3 4 5 6 7
   | module.exports = {   plugins: [     new HtmlWebpackPlugin({       template: './public/index.html',     }),   ], }
  | 
再执行npm run build,会看到生成了一个index.html文件,打开它会发现app.js也被引入到文件且能正常运行
编译 JS 文件
ES6 或以上版本的语法有些浏览器还是不识别语法的,所以我们需要用 babel 对它进行编译,编译成 ES5 代码
在src/index.js写一个 ES6 的箭头函数
1 2 3
   | const func = () => {   return 100 }
  | 
安装 babel 插件
1
   | yarn add babel-loader @babel/core @babel/preset-env -D
   | 
- babel-loader:使用 Babel 和 webpack 来转译 JavaScript 文件。
 - @babel/preset-env:将语法翻译成 es5 语法
 - @babel/core:babel 的核心模块
 
在根目录下新建.babelrc
1 2 3
   | {   "presets": ["@babel/preset-env"] }
  | 
在webpack.config.js中配置解析 js 的规则
1 2 3 4 5 6 7 8 9 10 11
   | module.exports = {   module: {     rules: [       {         test: /\.(js|jsx)$/,         loader: 'babel-loader',         exclude: /node_modules/,       },     ],   }, }
  | 
执行npm run build,查看app.js已经把箭头函数编译成 ES5 语法了
1 2 3 4 5 6 7
   |  ;(function (module, exports) {   eval(     "console.log('hello webpack');\n\nvar func = function func() {\n  return 100;\n};\n\n//# sourceURL=webpack:///./src/index.js?"   )
     })
  | 
处理浏览器没有的新特性
有些浏览器没有 Promise、Symbol、Map 等一些新特性,就需要使用 polyfill 来实现;
首先来理解下 babel 和 babel polyfill 的关系:
- babel 解决语法层面的问题,比如将 ES6+语法转为 ES5 语法。
 - babel polyfill 解决API层面的问题,需要使用垫片,比如下面三种:
 
@babel/polyfill:通过向全局对象和内置对象的 prototype 上添加方法来实现的,从而解决了低版本浏览器无法实现的一些 es6+语法;但污染了全局空间和内置对象也是个缺点。
@babel/runtime:不会污染全局空间和内置对象原型,但如果使用的特性非常多,就会导致这种按需引入非常繁琐。由于它是 polyfill 的包,适合在组件,类库中使用,所以需要添加到生产环境中。
@babel/plugin-transform-runtime:将 helper 和 polyfill 都改为从一个统一的地方引入,解决了全局空间和内置对象的问题;需要用到两个依赖包 @babel/runtime和@babel/runtime-corejs3——加载 ES6+ 新的 API(corejs 根据你的版本)
babel-runtime 适合在组件,类库项目中使用,而 babel-polyfill 适合在业务项目中使用。
polyfill 用到的 core-js 是可以指定版本的,比如使用 core-js@3 core-js@2
1 2 3
   | yarn add @babel/polyfill core-js@3 yarn add @babel/plugin-transform-runtime -D yarn add @babel/runtime @babel/runtime-corejs3
   | 
安装完毕后,在 .babelrc进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | {   "presets": [     [       "@babel/preset-env",       {         "useBuiltIns": "usage", // 按需引入,它能自动给每个文件添加其需要的polyfill         "corejs": 3 // 根据你的版本来写       }     ]   ],   "plugins": [     [       "@babel/plugin-transform-runtime",       {         "corejs": 3,         "helpers": true, // 开启内联的babel helpers(即babel或者环境本来的存在的垫片或者某些对象方法函数)         "regenerator": true, // 开启generator函数转换成使用regenerator runtime来避免污染全局域         "useESModules": false       }     ]   ] }
  | 
编译 React
安装 react 有关包和编译 React 的 babel 插件
1 2
   | yarn add react react-dom yarn add @babel/preset-react -D
   | 
重写src/index.js
1 2 3 4 5
   | import React from 'react' import ReactDOM from 'react-dom' import App from './App.jsx'
  ReactDOM.render(<App />, document.getElementById('root'))
   | 
新建文件src/App.jsx
1 2 3 4 5
   | import React from 'react'
  const App = () => <div>App</div>
  export default App
   | 
修改.babelrc文件(presets 的顺序从右向左)
1 2 3 4 5 6 7 8 9 10 11 12
   | {   "presets": [     [       "@babel/preset-env",       {         "useBuiltIns": "usage", // 按需引入,它能自动给每个文件添加其需要的polyfill         "corejs": 3 // 根据你的版本来写       }     ],     "@babel/preset-react"   ], }
  | 
执行打包命令,成功编译
devServer 配置
安装 devServer,在开发中实时检测文件的变化
1
   | yarn add webpack-dev-server -D
   | 
在``package.json`中设置启动命令
1
   | "dev": "webpack-dev-server",
   | 
webpack.config.js
1 2 3 4 5 6 7 8 9 10 11
   | module.exports = {   devServer: {     publicPath: '/',     host: '0.0.0.0',     compress: true,      port: 8080,      historyApiFallback: true,     overlay: true,      hot: true,    }, }
  | 
开启服务器yarn dev,打开http://localhost:8080,就可以 React 组件正常编译运行了。
开发环境热更新
1
   | yarn add -D react-hot-loader
   | 
在webpack.config.js配置,devServer 上面已经开启hot:true
1 2 3
   | plugins: [   new webpack.HotModuleReplacementPlugin(), ],
   | 
在src/index.js中添加
1 2 3 4 5 6 7 8 9
   | import React from 'react' import ReactDOM from 'react-dom' import App from './App'
  if (module && module.hot) {   module.hot.accept() }
  ReactDOM.render(<App />, document.getElementById('root'))
   | 
重新运行yarn dev,热更新生效
省略文件后缀和设置别名
上面引入 App 组件我们是import App from './App.jsx'加了后缀,如果省略的话会报错;我们可以在 webpack 配置文件的resolve设置可以省略后缀;
还有一个问题,如果项目较大,组件层级关系多的时候,我们可以给路径设置别名
新建src/components/Header/index.js
1 2 3 4 5
   | import React from 'react'
  const Header = () => <div>Header</div>
  export default Header
   | 
在 webpack.config.js 进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13
   | module.exports = {   mode: 'development',   entry: './src/index.js',   output: {     path: path.resolve(__dirname, 'dist'),     filename: 'app.js',   },   resolve: {     extensions: ['.jsx', '.js', '.json'],     alias: {       '@components': path.resolve(__dirname, 'src/components'),     },   },
  | 
修改App.jsx
1 2 3 4 5 6 7 8 9 10
   | import React from 'react' import Header from '@components/Header'
  const App = () => (   <div>     <Header />   </div> )
  export default App
   | 
加载 Css
1
   | yarn add style-loader css-loader -D
   | 
给 Header 组件添加样式,新建文件src/components/Header/index.css,再到 Header 组件中引入import './index.css'
1 2 3
   | div {   background-color: lightgreen; }
  | 
- css-loader 的作用是处理 css 文件中 @import,url 之类的语句(将 CSS 转化成 CommonJS 模块)
 - style-loader 则是将 css 文件内容放在 style 标签内并插入 head 中
 
webpack.config.js配置(loader 执行顺序从右到左)
1 2 3 4 5 6 7 8 9
   |  module: {     rules: [       {         test: /\.css$/,         use: ['style-loader', 'css-loader'],       },     ],   }, }
  | 
支持 Sass
1
   | yarn add sass sass-loader -D
   | 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | module: {   rules: [     {       test: /\.(sass|scss)$/,       use: [         'style-loader',         'css-loader',         {           loader: 'sass-loader',           options: {             implementation: require('sass'),            },         },       ],     },   ], },
  | 
把src/components/Header/index.css改为.scss文件,Header 组件index.js的引入也修改一下引入 css 文件名,然后执行打包命令,OK。
less 就不写了,毕竟一个项目选一种就行了。
集成 Postcss
postcss-loader 作用:
- 把 css 解析为一个抽象语法树
 - 调用插件处理抽象语法树并添加功能,如处理 CSS 兼容添加一些新特性的厂商前缀
 
1
   | yarn add postcss-loader autoprefixer -D
   | 
在根目录下新建postcss.config.js
1 2 3 4 5 6 7 8
   | module.exports = {   plugins: [     require('autoprefixer')({              overrideBrowserslist: ['last 2 versions', '>1%'],     }),   ], }
  | 
webpack.config.js中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | module: {   rules: [     {       test: /\.css$/,       use: ['style-loader', 'css-loader', 'postcss-loader'],     },     {       test: /\.(sass|scss)$/,       use: [         'style-loader',         'css-loader',         'postcss-loader',         {           loader: 'sass-loader',           options: {             implementation: require('sass'),            },         },       ],     },   ] }
  | 
在样式文件添加 CSS3 一些新特性,就能看到被自动添加了兼容处理了。
加载图片
1
   | yarn add file-loader url-loader -D
   | 
加入一张图片src/components/Header/img/fe-house-qrcode.jpg,在src/components/Header/index.js中引入
1
   | import './img/fe-house-qrcode.jpg'
   | 
在webpack.config.js中进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | module: {   rules: [     {       test: /\.(png|jpe?g|gif)$/,       use: {         loader: 'url-loader',         options: {           name: '[name]_[hash].[ext]',           outputPath: 'images/',            limit: 102400,          },       },     },   ] }
  | 
解析字体图标
这里就不演示了,直接上配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | module: {   rules: [     {       test: /\.(eot|ttf|svg|woff|woff2)$/,       use: {         loader: 'file-loader',         options: {           name: '[name]_[hash].[ext]',           outputPath: 'fonts/',         },       },     },   ] }
  | 
区分生产环境和开发环境
到这里,最基本配置差不多可以用了,接下来需要做一些细节或优化的配置。
安装webpack-merge整合
1
   | yarn add webpack-merge -D
   | 
提炼出三个文件:
- webpack.common.js 公共配置
 - webpack.dev.js 开发环境配置
 - webpack.prod.js 生产环境配置
 
将webpack.config.js的东西提炼出来分三个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
   | const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin')
  module.exports = {   mode: 'development',   entry: './src/index.js',   output: {     path: path.resolve(__dirname, 'dist'),     filename: 'app.js',   },   resolve: {     extensions: ['.jsx', '.js', '.json'],     alias: {       '@components': path.resolve(__dirname, 'src/components'),     },   },   module: {     rules: [       {         test: /\.(js|jsx)$/,         loader: 'babel-loader',         exclude: /node_modules/,       },       {         test: /\.css$/,         use: ['style-loader', 'css-loader', 'postcss-loader'],       },       {         test: /\.(sass|scss)$/,         use: [           'style-loader',           'css-loader',           'postcss-loader',           {             loader: 'sass-loader',             options: {               implementation: require('sass'),              },           },         ],       },       {         test: /\.(png|jpe?g|gif)$/,         use: {           loader: 'url-loader',           options: {             name: '[name]_[hash].[ext]',             outputPath: 'images/',              limit: 102400,            },         },       },       {         test: /\.(eot|ttf|svg|woff|woff2)$/,         use: {           loader: 'file-loader',           options: {             name: '[name]_[hash].[ext]',             outputPath: 'fonts/',           },         },       },     ],   },   plugins: [     new HtmlWebpackPlugin({       template: './public/index.html',     }),   ],   devServer: {     publicPath: '/',     host: '0.0.0.0',     compress: true,      port: 8080,      historyApiFallback: true,     overlay: true,      hot: true,    }, }
  | 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | const webpack = require('webpack') const { merge } = require('webpack-merge') const commonConfig = require('./webpack.common') const devConfig = {   mode: 'development',   devtool: 'cheap-module-eval-source-map',    plugins: [new webpack.HotModuleReplacementPlugin()],   devServer: {     publicPath: '/',     host: '0.0.0.0',     compress: true,      port: 8080,      historyApiFallback: true,     overlay: true,      hot: true,    }, }
  module.exports = merge(commonConfig, devConfig)
  | 
1 2 3 4 5 6 7 8
   | const { merge } = require('webpack-merge') const commonConfig = require('./webpack.common') const devConfig = {   mode: 'production',   devtool: 'none', }
  module.exports = merge(commonConfig, devConfig)
  | 
修改package.json
1 2 3 4
   | "scripts": {   "dev": "webpack-dev-server --config webpack.dev.js",   "build": "npx webpack --config webpack.prod.js" },
  | 
大功告成。
打包前清理 dist 目录
在你调试打包的过程可能会多出一些遗留文件,我们想要最新打包编译的文件,就需要先清除 dist 目录
1
   | yarn add clean-webpack-plugin -D
   | 
加到webpack.common.js文件
1 2 3 4
   | const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = {   plugins: [new CleanWebpackPlugin()], }
  | 
项目规范
作为项目代码风格的统一规范,我们需要借助第三方工具来强制,让团队成员的代码风格更趋于一致。
EditorConfig
.editorconfig 是跨编辑器维护一致编码风格的配置文件,在 VSCode 中需要安装相应插件EditorConfig for VS Code,安装完毕之后,
使用快捷键ctrl+shift+p打开命令台,输入 Generate .editorcofig 即可快速生成 .editorconfig 文件。
在.editorcofig文件,就可以大家根据不同来设置文件了,比如我的是这样:
1 2 3 4 5 6 7 8 9
   | root = true
  [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf
   | 
Prettier
同样也需要安装 VSCode 插件Prettier - Code formatter
安装完成后在根目录下新建文件夹.vscode,在该文件夹下新建文件settings.json,该文件的配置优先于你自己 VSCode 全局的配置,不会因为团队不同成员的 VSCode 全局配置不同而导致格式化不同。
settings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | {   // 指定哪些文件不参与搜索   "search.exclude": {     "**/node_modules": true,     "dist": true,     "yarn.lock": true   },   "editor.formatOnSave": true,   "eslint.validate": ["javascript", "javascriptreact"],   "editor.codeActionsOnSave": {     "source.fixAll.eslint": true,   },   "[javascript]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[javascriptreact]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[json]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[html]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[markdown]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[css]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[less]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   },   "[scss]": {     "editor.defaultFormatter": "esbenp.prettier-vscode"   } }
  | 
安装完后新建文件.prettierrc
1 2 3 4 5 6 7 8 9 10
   | {   "trailingComma": "all",   "tabWidth": 2,   "semi": false,   "singleQuote": true,   "endOfLine": "lf",   "printWidth": 120,   "bracketSpacing": true,   "arrowParens": "avoid" }
  | 
ESLint
安装完后运行npx eslint --init,运行后有选项,选择如下(自行根据需要):
- To check syntax, find problems, and enforce code style
 - JavaScript modules (import/export)
 - React
 - No(这里我们没使用 TS 就不必了)
 - Browser Node
 - Use a popular style guide
 - Airbnb: https://github.com/airbnb/javascript
 - JavaScript
 - Would you like to install them now with npm(Yes)直接安装
 
完毕后会发现已经自动安装了 ESLint 相关的包,也自动生成了.eslintrc.js文件,里面的rules默认是空,这个就需要团队自己的额外配置了。注:VSCode 需要安装ESLint插件
新建文件.eslintignore,忽略一些文件的检查
1 2 3
   | /node_modules /public /dist
   | 
StyleLint
上面是针对规范 JS 代码的,接下来规范 CSS 代码,同样需要安装 VSCode 插件stylelint
1
   | yarn add stylelint stylelint-config-standard -D
   | 
新建文件.stylelintrc.js
1 2 3 4 5 6 7 8 9 10 11 12
   | module.exports = {   extends: ['stylelint-config-standard'],   rules: {     'comment-empty-line-before': null,     'declaration-empty-line-before': null,     'function-name-case': 'lower',     'no-descending-specificity': null,     'no-invalid-double-slash-comments': null,     'rule-empty-line-before': 'always',   },   ignoreFiles: ['node_modules/**/*', 'dist/**/*'], }
  | 
Prettier、ESLint、Stylelint 冲突解决
上面一顿操作后,可能发现 ESLint 给文件飘红,但保存后格式变了又好像恢复原样,这个就是冲突问题,一般解决就是利用插件禁用与 Prettier 起冲突的规则。
1
   | yarn add eslint-config-prettier -D
   | 
修改.eslintrc.js
1 2 3 4 5 6 7
   | module.exports = {   extends: [          'prettier',     'prettier/react',   ], }
  | 
如果代码还因为 ESLint 飘红,可以看情况添加忽略的规则。
1
   | yarn add stylelint-config-prettier -D
   | 
修改.stylelintrc.js文件中增加一个 extend
1
   | extends: ['stylelint-config-prettier'],
   | 
结语
写到这里就停了,当然要继续往下做的话还有很多,比如用 husky 代码提交,还有很多像 webpack 压缩分离 JS 和 CSS 等一些,这些留到以后写一篇 webpack 性能优化再来一起总结。
react-cli Github 地址