10

从零开始的 webpack4 教程

前提条件

安装 node.js
当前 node.js 版本 :v12.13.1
当前 npm 版本 : 6.12.1

2020/1/6 更新 自己重新按照文档走一遍发现有点遗漏,进行补充

本文从零开始搭建 webpack ,只需要按照步骤一步一步走,最后就可搭建成功 ,请放心食用,无毒

完整源码

一、 简易打包

1.初始化项目 & 创建项目

mkdir webpack4-react && cd webpack4-react
npm init -y

2.安装 webpack & webpack 的 cli

当前 webpack 版本:4.41.5
当前 webpack-cli 版本:3.3.10

npm install --save-dev webpack webpack-cli

yarn add webpack webpack-cli --dev
调整 package.json 文件,以便确保我们安装包是私有的(private),并且移除 main 入口。这可以防止意外发布你的代码。

package.json

{
  ...
  "description": "webpack4-react",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  ...
}

3.下载 lodash 依赖

npm install --save lodash

yarn add lodash

4.创建以下目录结构、文件和内容:

webpack4-react
|- package.json
|- /dist
  |- index.html
|- /src
  |- index.js

package.json

{
  "name": "webpack4-react",
  "version": "1.0.0",
  "description": "webpack4-react",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "lodash": "^4.17.15"
  },
  "devDependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

dist/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>webpack4-react</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

src/index.js

import _ from 'lodash';
function component() {
  var element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;
}

document.body.appendChild(component());

5. 执行打包命令

npx webpack
将看到以下输出:

Hash: 17a14a12467064d9d4dd
Version: webpack 4.41.5
Time: 1239ms
Built at: 2020-01-04 10:56:16
  Asset      Size  Chunks             Chunk Names
main.js  72.1 KiB       0  [emitted]  main
Entrypoint main = main.js
[1] ./src/index.js 210 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]
    + 1 hidden module

此时在 dist 文件夹下已经生成一个 main.js 文件
在浏览器中打开 dist 下的 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello webpack'。

简易打包已经完成

二、使用配置文件打包

1. 添加 webpack.config.js 配置文件

webpack4-react
|- package.json
|- webpack.config.js
|- /dist
  |- index.html
|- /src
  |- index.js

webpack.config.js

const path = require('path');

module.exports = {
  entry: {
    //配置页面入口
    index: ['./src/index.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

dist/index.html

...
<body>
  <script src="bundle.js"></script>
</body>
...

2. 添加 NPM 脚本

package.json

{
  "name": "webpack4-react",
  "version": "1.0.0",
  "description": "webpack4-react",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "lodash": "^4.17.15"
  },
  "devDependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

3.执行脚本命令打包

npm run build

终端将输出:

Hash: 9cbb2fac6cc224bfe661
Version: webpack 4.41.5
Time: 1272ms
Built at: 2020-01-04 11:39:26
  Asset      Size  Chunks             Chunk Names
main.js  72.1 KiB       0  [emitted]  main
Entrypoint main = main.js
[1] ./src/index.js 213 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]
    + 1 hidden module

同样的, dist 文件夹下生成 bundle.js 文件

这样就实现了基本的 webpack 构建了

三、集成 React

1. 下载 react 和 react-dom

npm install --save-dev react react-dom

2. 下载 babel(编译 js、jsx,es6 等)

npm install --save-dev @babel/cli @babel/core @babel/preset-react @babel/preset-env @babel/plugin-transform-runtime babel-loader

3. 修改 入口文件

src/index.js

import React from 'react';
import ReactDom from 'react-dom';

const hello = 'Hello React'
ReactDom.render(
    <div>
        <div>{hello}</div>
    </div>,
    document.getElementById('app'),
);

4. 添加 react 根元素

dist/index.html

...
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
...

5. 添加 babel 相关配置

为了使用 babel 解析 jsx
    1. webpack 配置文件中

    webpack.config.js

    ...
    entry: {
      //配置页面入口
      index: ['./src/index.js'],
    },
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'babel-loader',
              options: {
                presets: [
                  [
                    '@babel/preset-env',
                    {
                      useBuiltIns: 'usage',
                      corejs: 3,
                      targets: {
                        chrome: '58',
                        ie: '8',
                      },
                    },
                  ],
                  '@babel/preset-react',
                ],
              },
            },
          ],
        },
      ],
    }
    ...
    1. 添加 babel 配置文件
    在根目录下新建 .babelrc 文件

    .babelrc

    {
      "presets": [
        [
          "@babel/preset-env",
          {
            "useBuiltIns": "usage",
            "corejs": 3,
            "targets": {
              "chrome": "58",
              "ie": "8"
            }
          }
        ],
        "@babel/preset-react"
      ],
      "plugins": [
        [
          "@babel/plugin-transform-runtime",
          {
            "absoluteRuntime": false,
            "corejs": false,
            "helpers": true,
            "regenerator": true,
            "useESModules": false
          }
        ]
      ]
    }

    当前文件目录结构:

    webpack4-react
    |- /dist
      |- index.html
    |- /src
      |- index.js
    |- .babelrc
    |- package.json
    |- webpack.config.js

6. 打包

执行 npm run build
终端输出:
```
$ webpack
Hash: f4d46dd4732764195f93
Version: webpack 4.41.5
Time: 446ms
Built at: 2020-01-04 13:28:48
    Asset     Size  Chunks             Chunk Names
bundle.js  128 KiB       0  [emitted]  main
Entrypoint main = bundle.js
[3] ./src/index.js 211 bytes {0} [built]
    + 7 hidden modules
```

在浏览器中打开 dist 下的 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello React'。

四、建立开发环境

文件结构

webpack4-react
|- /src
  |- index.js
  |- index.html
|- .babelrc
|- package.json
|- webpack.config.js

src/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>webpack4-react</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

1. webpack-dev-server

    1. 下载依赖
    npm install --save-dev webpack-dev-server
    1. webpack 配置文件中配置 webpack-dev-server
      webpack.config.js
    ...
    entry: {
      //配置页面入口
      index: ['./src/index.js'],
    },
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    devServer: {
      contentBase: '/src',
      hot: true,
    },
    ...

2. html-webpack-plugin

    1. 下载依赖
    npm install --save-dev html-webpack-plugin
    1. webpack 配置文件中配置 webpack-dev-server
      webpack.config.js
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    ...
    devServer: {
      contentBase: '/src',
      hot: true,
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
        filename: './index.html',
        chunks: ['index'],
        inject: 'body',
      }),
    ],
    ...

3. 添加 NPM 脚本

package.json

{
  "name": "webpack4-react",
  "version": "1.0.0",
  "description": "webpack4-react",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --open --config webpack.config.js",
    "build": "webpack --config webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "lodash": "^4.17.15",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.7.7",
    "@babel/core": "^7.7.7",
    "@babel/plugin-transform-runtime": "^7.7.6",
    "@babel/preset-env": "^7.7.7",
    "@babel/preset-react": "^7.7.4",
    "babel-loader": "^8.0.6",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  }
}

4.执行脚本命令 npm run dev

执行 npm run dev 后会自动打开浏览器,此时修改 index.js 文件中内容,浏览器会实时更新

删除 dist 文件夹

执行 npm run build 打包依旧会在 dist 下生成打包文件

五、资源管理

1. 加载 CSS

为了从 JavaScript 模块中 import 一个 CSS 文件,你需要在 module 配置中 安装并添加 style-loader 和 css-loader:
    1. 下载 style-loader css-loader
    npm install --save-dev style-loader css-loader
    1. webpack.config.js 中配置 css 的 loader
        module: {
          rules: [
            {
              test: /\.css$/,
              use: [
              'style-loader',
              'css-loader',
              ]
            },
      ...
      }
    1. src/index.js 中引入 css

    src 下新建 style/reset.css
    style/reset.css

    div {
      padding: 0;
      margin: 0;
      font-size: 20px;
    }

    在 src/index.js

    import './style/reset.css';

    执行 npm run dev ,会看到 reset.css 中的样式已经生效

2. CSS 预处理器 & 模块化 & 兼容性处理

    1. 下载依赖
    npm install --save-dev autoprefixer postcss-loader
    npm install --save-dev less-loader node-sass sass sass-loader
    这个过程中安装 node-sass 可能会很慢, 耐心等待
    1. 创建 React 组件

    创建如下目录文件及内容:

    webpack4-react
    |- src
      |- components
        |- Date
          |- index.jsx
          |- style.scss
      |- style
        |- reset.scss
      |- index.js
    |- .babelrc
    |- package.json
    |- webpack.config.js

    src/components/Date/index.jsx

    import React from 'react';
    import style from './style.scss';
    
    class DateComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          date: new Date(),
        };
      }
    
      componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000);
      }
    
      componentWillUnmount() {
        clearInterval(this.timerID);
      }
    
      tick() {
        this.setState({
          date: new Date(),
        });
      }
    
      render() {
        const { date } = this.state;
        return (
         <div>
            <div className={style.title}>时间</div>
            <div className={style.title}>
            {date.toLocaleTimeString()}
            </div>
          </div>
        );
      }
    }
    export default DateComponent;

    src/components/Date/style.scss

    .title {
      height: 50px;
      font: bold 20px '微软雅黑';
      text-align: center;
      color: #000;
    }
    1. 引入组件

    src/index.js

    import React from 'react';
    import ReactDom from 'react-dom';
    import DateComponents from './components/Date/index.jsx';
    import './style/reset.scss';
    
    const hello = 'Hello React';
    ReactDom.render(
      <div>
        <div>{hello}</div>
        <DateComponents />
      </div>,
      document.getElementById('app')
    );
    1. 新增 CSS 相关的 webpack 配置
      根目录下新建 tools/utils.js
    // css 配置
    const styleLoader = {
      loader: 'style-loader',
    };
    const cssLoader = {
      loader: 'css-loader',
      options: {
        modules: true, // webpack3 为 module
        sourceMap: true,
        importLoaders: 2,
      },
    };
    const postCssLoader = {
      loader: 'postcss-loader',
    };
    const sassLoader = {
      loader: 'sass-loader',
      options: {
        sourceMap: true,
      },
    };
    const lessLoader = {
      loader: 'less-loader',
    };
    exports.loadersConfig = {
      styleLoader,
      cssLoader,
      postCssLoader,
      sassLoader,
      lessLoader,
    };
    // css 配置

    webpack.config.js

    ...
    const utils = require('./tools/utils.js');
    const {
      postCssLoader,
      styleLoader,
      sassLoader,
      cssLoader,
    } = utils.loadersConfig;
    ...
    module.exports = {
      ...
      module: {
        rules: [
          {
            test: /\.css$/,
            exclude: /node_modules/,
            use: [styleLoader, cssLoader, postCssLoader],
          },
          {
            test: /\.scss$/,
            include: [/pages/, /components/, /style/],
            use: [
              styleLoader,
              cssLoader,
              postCssLoader,
              sassLoader,
            ],
          },
        }
      ...
    };
    1. 新增 postcss-loader 配置文件
      根目录下新增 postcss.config.js
    const AUTOPREFIXER_BROWSERS = [
      'Android 2.3',
      'Android >= 4',
      'Chrome >= 35',
      'Firefox >= 31',
      'Explorer >= 8',
      'iOS >= 7',
      'Opera >= 12',
      'Safari >= 7.1',
    ];
    module.exports = {
      plugins: [require('autoprefixer')({overrideBrowserslist: ['> 0.15% in CN']})],
    };
这时候执行 npm run dev 命令会报错,因为缺少一些 babel 依赖,下载一下就好了
npm install --save @babel/runtime core-js

执行 npm run dev ,自动打开浏览器,css 相关的配置构建完成

3. 图片处理

    1. 下载依赖
    npm install --save-dev file-loader url-loader
    1. webpack 配置中添加规则
    ...
    module: {
      rules: [
          ...
          {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  name: '[path][name].[ext]',
                  limit: 1024 * 15,
                  fallback: 'file-loader',
                },
              },
            ],
          },
          ...
      ]
    }
    ...
    1. React 组件中 引入图片
      src/components/Date/index.jsx
    ...
    import reactLogo from './../../images/React.svg';
    import webpackLogo from './../../images/webpack.svg';
    ...
    render() {
      const { date } = this.state;
      return (
        <div>
          <div className={style.title}>时间</div>
          <div>
          <img className={style.logo} src={reactLogo} alt="" />
          <img className={style.logo} src={webpackLogo} alt="" />
          </div>
          <div className={style.title}>
          {date.toLocaleTimeString()}
          </div>
        </div>
      );
    }
    ...

4. 其他

字体、数据等参考 webpack 官网 资源管理

六、 resolve 配置

在代码引入组件或图片时,我们来配置一些便捷的方式

1. 配置

webpack.config.js

// 引入 node 的 path 模块
const path = require('path');
...
module.exports = {
  ...
  resolve: {
    // 设置模块导入规则,import/require时会直接在这些目录找文件
    modules: ['node_modules'],
    // import导入时省略后缀
    extensions: ['.js', '.jsx', '.scss', '.less', '.css', '.json'],
    // import导入时别名
    alias: {
      '@components': path.resolve('./src/components'),
      '@images': path.resolve('./src/images'),
      '@style': path.resolve('./src/style'),
    },
  },
  ...
}

2. 使用

举个 ?

src/index.js 中

import React from 'react';
import ReactDom from 'react-dom';
import DateComponents from '@components/Date/index.jsx';
import '@style/reset.scss';

const hello = 'Hello React';
ReactDom.render(
  <div>
    <div>{hello}</div>
    <DateComponents />
  </div>,
  document.getElementById('app')
);

此时 执行 npm run dev 查看

七、 环境配置构建

开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

1. 使用 webpack-merge 配置

    1. 下载依赖
    npm install --save-dev webpack-merge clean-webpack-plugin uglifyjs-webpack-plugin
    1. 拆分 webpack 配置

    根目录下创建 webpack 文件夹

    |- webpack
      |- webpack.common.js
      |- webpack.dev.js
      |- webpack.production.js

    webpack.common.js

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const utils = require('./../tools/utils');
    const { postCssLoader, styleLoader, sassLoader, cssLoader } = utils.loadersConfig;
    
    module.exports = {
      entry: {
        //配置页面入口
        index: ['./src/index.js'],
      },
      output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist'),
      },
      devServer: {
        contentBase: '/src',
        hot: true,
      },
      resolve: {
        // 设置模块导入规则,import/require时会直接在这些目录找文件
        modules: ['node_modules'],
        // import导入时省略后缀
        extensions: ['.js', '.jsx', '.scss', '.less', '.css', '.json'],
        // import导入时别名
        alias: {
        '@assets': path.resolve('./src/assets'),
        '@common': path.resolve('./src/common'),
        '@components': path.resolve('./src/components'),
        '@images': path.resolve('./src/images'),
        '@pages': path.resolve('./src/pages'),
        '@style': path.resolve('./src/style'),
        },
      },
      plugins: [
        new HtmlWebpackPlugin({
        template: './src/index.html',
        filename: './index.html',
        chunks: ['index'],
        inject: 'body',
        }),
      ],
      module: {
        rules: [
          {
            test: /\.css$/,
            exclude: /node_modules/,
            use: [styleLoader, cssLoader, postCssLoader],
          },
          {
            test: /\.scss$/,
            include: [/pages/, /components/, /style/],
            use: [styleLoader, cssLoader, postCssLoader, sassLoader],
          },
          {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  name: '[path][name].[ext]',
                  limit: 1024 * 15,
                  fallback: 'file-loader',
                },
              },
            ],
          },
          {
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'babel-loader',
                options: {
                  presets: [
                    [
                      '@babel/preset-env',
                      {
                          useBuiltIns: 'usage',
                          corejs: 3,
                            targets: {
                            chrome: '58',
                            ie: '8',
                          },
                      },
                    ],
                    '@babel/preset-react',
                  ],
                },
              },
            ],
          },
        ],
      },
    };

    webpack.dev.js

    const merge = require('webpack-merge');
    const common = require('./webpack.common.js');
    module.exports = merge(common, {
      mode: 'development',
      devtool: 'inline-source-map',
      devServer: {
        contentBase: '/src',
        hot: true,
      }
    });

    webpack.production.js

    const merge = require('webpack-merge');
    const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); // 用来缩小(压缩优化)js文件
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const common = require('./webpack.common.js');
    module.exports = merge(common, {
      mode: 'production',
      devtool: 'source-map',
      plugins: [
        new UglifyJSPlugin({
          sourceMap: true,
        }),
        new CleanWebpackPlugin(),
      ],
    });
    1. npm 脚本命令更改
      package.json
    "dev": "webpack-dev-server --open --config webpack/webpack.dev.js",
    "build": "webpack --config webpack/webpack.production.js"
    1. 设置环境变量
      下载依赖
    npm install --save-dev cross-env

    npm 脚本命令更改

    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --config webpack/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.production.js"

八、 其他优化

1. 配置中获取环境变量

在 npm 脚本执行的时候设置的环境变量通过 process.env.NODE_ENV 来获取,process.env.NODE_ENV 的值 在当前脚本下有两种: development , production , 借此可以根据不同环境设置不同的配置。

2. 添加 html-loader 及 html 优化

    1. 下载依赖
    npm install --save-dev html-loader
    1. 配置

    在根目录下添加一个 react.ico 的图片待用,用于在 HtmlWebpackPlugin 中配置网页的 ico 图片

    webpack.config.common.js

    ...
    entry: {
      //配置页面入口
      index: ['./src/index.js'],
    },
    output: {
      //配置输出选项
      path: path.resolve(__dirname, '../dist'), //输出路径为,当前路径下
      filename: '[name].[hash:5].js', //输出后的文件名称
    },
    ...
    plugins: [
      new HtmlWebpackPlugin({
        title: 'webpack & react',
        template: './src/index.html', //本地模板文件的位置,支持加载器(如handlebars、ejs、undersore、html等),如比如 handlebars!src/index.hbs;
        filename: './index.html', //输出文件的文件名称,默认为index.html,不配置就是该文件名;此外,还可以为输出文件指定目录位置(例如'html/index.html')
        chunks: ['index'], // chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件
        inject: 'body', //1、true或者body:所有JavaScript资源插入到body元素的底部2、head: 所有JavaScript资源插入到head元素中3、false: 所有静态资源css和JavaScript都不会注入到模板文件中
        showErrors: true, //是否将错误信息输出到html页面中
        hash: false, //是否为所有注入的静态资源添加webpack每次编译产生的唯一hash值
        favicon: 'react.ico', //添加特定的 favicon 路径到输出的 HTML 文件中。
        minify: {
          //是否对大小写敏感,默认false
          caseSensitive: true,
          //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled  默认false
          collapseBooleanAttributes: true,
          //是否去除空格,默认false
          collapseWhitespace: true,
          //是否压缩html里的css(使用clean-css进行的压缩) 默认值false;
          minifyCSS: true,
          //是否压缩html里的js(使用uglify-js进行的压缩)
          minifyJS: true,
          //Prevents the escaping of the values of attributes
          preventAttributesEscaping: true,
          //是否移除属性的引号 默认false
          removeAttributeQuotes: true,
          //是否移除注释 默认false
          removeComments: true,
          //从脚本和样式删除的注释 默认false
          removeCommentsFromCDATA: true,
          //是否删除空属性,默认false
          removeEmptyAttributes: true,
          //  若开启此项,生成的html中没有 body 和 head,html也未闭合
          removeOptionalTags: false,
          //删除多余的属性
          removeRedundantAttributes: true,
          //删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false
          removeScriptTypeAttributes: true,
          //删除style的类型属性, type="text/css" 同上
          removeStyleLinkTypeAttributes: true,
          //使用短的文档类型,默认false
          useShortDoctype: true,
        },
    }),
    ]
    ...
    module: {
      rules: [
        {
          test: /\.html$/,
          use: 'html-loader',
        },
      ]
    }

3. 压缩 提取 CSS

    1. 下载依赖
    npm install --save-dev mini-css-extract-plugin
    1. 在生产环境中压缩 CSS, 开发环境中不压缩 scss

    webpack.config.common.js 其中 关于 .scss&dollar; 的 rules 替换下

    ...
    const devMode = process.env.NODE_ENV !== 'production';
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    ...
    module.exports = {
      ...
      plugins: [
        ...
        new MiniCssExtractPlugin({
          filename: devMode ? '[name].css' : '[name]_[hash:5].css',
          chunkFilename: devMode ? '[id].css' : '[id]_[hash:5].css',
          disable: false, //是否禁用此插件
          allChunks: true,
        }),
        ...
      ]
      ...
      module: {
        rules: [
          {
            test: /\.html$/,
            use: "html-loader"
          },
          {
            test: /\.css$/,
            exclude: /node_modules/,
            use: [styleLoader, cssLoader, postCssLoader]
          },
          {
            test: /\.scss$/,
            include: [/pages/, /components/, /style/],
            use: [
              devMode ? styleLoader : MiniCssExtractPlugin.loader,
              cssLoader,
              postCssLoader,
              sassLoader,
            ],
          },
        ],
      }
    1. 再次执行 npm run build 会发现在 dist 文件夹里多了些 css,css.map 文件

4. 生产环境压缩 JS, 打包时清除 dist 文件夹

    1. 下载依赖
    npm install --save-dev uglifyjs-webpack-plugin clean-webpack-plugin
    1. webpack 配置

    webpack.production.js

    ...
    const merge = require('webpack-merge');
    const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const common = require('./webpack.config.common.js');
    module.exports = merge(common, {
      mode: 'production',
      devtool: 'source-map',
      plugins: [
        new UglifyJSPlugin({
          sourceMap: true,
        }),
        new CleanWebpackPlugin(),
      ],
    });
    ...

5. happypack 加快打包速度

    1. 下载依赖
    npm install --save-dev happypack
    1. 配置

    webpack.config.common.js

    ...
    const os = require('os');
    const HappyPack = require('happypack');
    const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
    ...
    plugins: [
      ...
      new HappyPack({
        id: 'babel', //用id来标识 happypack处理那里类文件
        threadPool: happyThreadPool, //共享进程池
        loaders: [
          {
            loader: 'babel-loader',
          },
        ],
      }),
      ...
    ],
    ...
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          use: ['happypack/loader?id=babel'],
          exclude: /node_modules/, //设置node_modules里的js文件不用解析
        },
      ]
    }
    ...

5. polyfill 编译 es6 的新语法

    1. 下载依赖
    npm install --save-dev @babel/polyfill @babel/plugin-transform-arrow-functions @babel/preset-es2017
    1. 配置

    webpack.common.js

    entry: {
      //配置页面入口
      index: ['@babel/polyfill', './src/index.js'],
    },
    1. 测试语法支持

    src/index.js

    async function f() {
      return 'hello world';
    }
    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    const hw = helloWorldGenerator();
    console.log('Generator>>>>>>', hw.next());
    const arr = [1, 2, 3, 4, 5, 1, 2, 3, 5];
    const setArr = new Set(arr);
    console.log('setArr?>>>>>>', setArr);
    const m = new Map();
    console.log('Map>>>>>>>>', m);
    f().then(v => console.log('async>>>>', v));
    // IE 不支持 Symbol
    const helloSymbol = Symbol('www');
    console.log('Symbol>>>>>>', helloSymbol);
    console.log('flat>>>>>>', [1, [2, [3]]].flat(Infinity));
    
    console.log('---------------');
    console.log('Promise');
    new Promise(resolve => {
      setTimeout(() => {
        resolve('hello');
      }, 2000);
    }).then(value => {
      console.log(value);
      return new Promise(resolve => {
        setTimeout(() => {
          resolve('world');
        }, 2000);
      });
    }).then(value => {
      console.log(`${value} world`);
    });
    console.log('---------------');
    
    const target = {};
    const handler = {};
    const proxy = new Proxy(target, handler);
    proxy.a = 'b';
    console.log('proxy>>>>>', target.a);
    // 不支持
    // class A {
    //     static name = 'name';
    // }
    // console.log('static class>>>>>', new A());

    打包 npm run build , 把 dist 文件的 index.html 用 IE 打开验证

九、 代码规范- eslint & stylelint

1. 添加编辑器配置文件以及插件

vs code 格式化插件 使用的是 Prettier - Code formatter 以及 ESLint

|- .vscode
    |- setting.json

setting.json

{
  "editor.tabSize": 4,
  "prettier.singleQuote": true,
  "editor.detectIndentation": false,
  "editor.renderControlCharacters": true,
  "editor.renderWhitespace": "all",
  "emmet.includeLanguages": {
    "javascript": "javascriptreact"
  },
  "prettier.trailingComma": "es5",
  "emmet.triggerExpansionOnTab": true,
  "javascript.implicitProjectConfig.experimentalDecorators": true,
  "workbench.colorTheme": "Solarized Light",
  "window.zoomLevel": 0,
  "prettier.useTabs": true,
  "editor.foldingStrategy": "indentation",
  "explorer.confirmDelete": false,
  "javascript.updateImportsOnFileMove.enabled": "never",
  "eslint.validate": [
    {
      "language": "javascript",
      "autoFix": true
    },
    {
      "language": "javascriptreact",
      "autoFix": true
    }
  ],
  "eslint.autoFixOnSave": true
}

2. eslint

    1. 下载依赖
    npm i babel-eslint eslint eslint-config-airbnb eslint-config-react-app eslint-friendly-formatter eslint-loader eslint-plugin-flowtype eslint-plugin-html eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react autoprefixer -D
    1. webpack 的 eslint 配置

    根目录下新建 .eslintrc.js 文件

    module.exports = {
      root: true,
      env: {
        browser: true,
        commonjs: true,
        es6: true,
      },
      extends: [
        'airbnb',
      ],
      globals: {
        $: true,
        process: true,
        __dirname: true,
      },
      parser: 'babel-eslint',
      parserOptions: {
        //es6的module模式
        sourceType: 'module',
        ecmaFeatures: {
          experimentalObjectRestSpread: true,
          jsx: true,
        },
        ecmaVersion: 9,
      },
      settings: {
        'import/ignore': ['node_modules', '.s?css', '@w*'],
      },
      // "excludedFiles": "*.test.js",
      plugins: ['react', 'import', 'jsx-a11y'],
      rules: {
        'import/no-unresolved': 0,
        'import/extensions': 0,
        'import/prefer-default-export': 0,
    
        'react/prop-types': 0,
        'react/jsx-filename-extension': 0,
        'react/prefer-stateless-function': 0,
        'react/jsx-indent': [2, 'tab'],
        'react/jsx-indent-props': [2, 'tab'],
        'react/require-default-props': 0,
        // // @off 同构应用需要在 didMount 里写 setState
        'react/no-did-mount-set-state': 0,
    
        'jsx-a11y/anchor-is-valid': 0,
        'jsx-a11y/click-events-have-key-events': 0,
        'jsx-a11y/mouse-events-have-key-events': 0,
        'jsx-a11y/no-noninteractive-element-interactions': 0,
        'jsx-a11y/no-static-element-interactions': 0,
    
        'no-return-assign': 0,
        'no-console': 0,
        // 0、1、2分别表示不开启检查、警告、错误
        indent: [2, 'tab', { SwitchCase: 1 }], // tab缩进
        // 圈复杂度
        complexity: [2, 9],
        'max-params': [2, 7],
        'max-depth': [2, 4],
        'max-len': [
          'error',
          {
            code: 150,
            tabWidth: 4,
            ignoreComments: true,
            ignoreUrls: true,
            ignoreStrings: true,
            ignoreTemplateLiterals: true,
            ignoreRegExpLiterals: true,
          },
        ],
        'no-tabs': 0,
        'object-curly-newline': [
          0,
          {
            ObjectExpression: 'always',
            ObjectPattern: { multiline: true },
            ImportDeclaration: 'never',
            ExportDeclaration: {
              multiline: true,
            },
          },
        ],
        'object-curly-spacing': 0,
    
        'arrow-parens': [2, 'as-needed'],
        // 最大回调层数
        'max-nested-callbacks': [2, 3],
        'no-unused-vars': [
          2,
          {
            argsIgnorePattern: '^React',
            varsIgnorePattern: '[Rr]eact|[Ss]tyle',
          },
        ],
        'no-extra-boolean-cast': 0,
        'array-callback-return': 0,
        'no-param-reassign': 0,
        'jsx-quotes': [0, 'prefer-double'], //强制在JSX属性(jsx-quotes)中一致使用双引号
        'no-underscore-dangle': 0,
        'quote-props': 0,
        // "no-native-reassign": 2,//不能重写native对象
        // // if while function 后面的{必须与if在同一行,java风格。
        // "brace-style": [2, "1tbs", { "allowSingleLine": true }],
        // // 双峰驼命名格式
        // "camelcase": 2,
        // // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
        // "computed-property-spacing": [2,"never"],
        // //允许箭头函数可以省略小括号
        // 'arrow-parens': 0,
        // 'no-extra-semi': 2, // 不允许多余的分号
        // //允许使用async-await函数
        // 'generator-star-spacing': 0,
        // //在开发环境开启debugger功能,生产环境禁止使用debugger
        // 'no-debugger': process.env.NODE_ENV === 'development' ? 0 : 2,
        // "quotes": [2, "single"], //单引号
        // "no-var": 2, //对var警告
        // "semi": ["error", "always"], //不强制使用分号
        // "no-irregular-whitespace": 0, //不规则的空白不允许
        // "no-alert": 2, //禁止使用alert confirm prompt
        // "no-lone-blocks": 0, //禁止不必要的嵌套块
        // "no-class-assign": 2, //禁止给类赋值
        // "no-cond-assign": 2, //禁止在条件表达式中使用赋值语句
        // "no-const-assign": 2, //禁止修改const声明的变量
        // "no-delete-var": 2, //不能对var声明的变量使用delete操作符
        // "no-dupe-keys": 2, //在创建对象字面量时不允许键重复
        // "no-duplicate-case": 2, //switch中的case标签不能重复
        // "no-dupe-args": 2, //函数参数不能重复
        // "no-empty": 2, //块语句中的内容不能为空
        // "no-func-assign": 2, //禁止重复的函数声明
        // "no-invalid-this": 0, //禁止无效的this,只能用在构造器,类,对象字面量
        // "no-redeclare": 2, //禁止重复声明变量
        // "no-spaced-func": 2, //函数调用时 函数名与()之间不能有空格
        // "no-this-before-super": 0, //在调用super()之前不能使用this或super
        // "no-undef": 2, //不能有未定义的变量
        // "no-use-before-define": 2, //未定义前不能使用
        // // "camelcase": 0, //强制驼峰法命名
        // "no-mixed-spaces-and-tabs": 0, //禁止混用tab和空格
        // "prefer-arrow-callback": 0, //比较喜欢箭头回调
        // "arrow-spacing": 0, //=>的前/后括号
        //
        // // 禁止在 componentDidMount 里面使用 setState
    
        // // 禁止在 componentDidUpdate 里面使用 setState
        // 'react/no-did-update-set-state': 2,
        // // 禁止拼写错误
    
        // 'react/no-typos': 2,
        // // 禁止使用字符串 ref
        // 'react/no-string-refs': 2,
        // // @fixable 禁止出现 HTML 中的属性,如 class
        // 'react/no-unknown-property': 2,
        // // 禁止出现未使用的 propTypes
        // // @off 不强制要求写 propTypes
        // 'react/no-unused-prop-types': 2,
        // // 出现 jsx 的地方必须 import React
        // // @off 已经在 no-undef 中限制了
        // 'react/react-in-jsx-scope': 0,
        // // 非 required 的 prop 必须有 defaultProps
        // // @off 不强制要求写 propTypes
        // 'react/require-default-props': 0,
        // // render 方法中必须有返回值
        // 'react/require-render-return': 2,
        // // @fixable 组件内没有 children 时,必须使用自闭和写法
        // // @off 没必要限制
        // 'react/self-closing-comp': 0,
        // // style 属性的取值必须是 object
        // 'react/style-prop-object': 2,
        // // HTML 中的自闭和标签禁止有 children
        // 'react/void-dom-elements-no-children': 2,
        // // 数组中的 jsx 必须有 key
        // 'react/jsx-key': 2,
        // // 禁止在 jsx 中使用像注释的字符串
        // 'react/jsx-no-comment-textnodes': 2,
        // // 禁止出现重复的 props
        // 'react/jsx-no-duplicate-props': 2,
        // // 禁止使用未定义的 jsx elemet
        // 'react/jsx-no-undef': 2,
        // // jsx 文件必须 import React
        // 'react/jsx-uses-react': 2,
        // // 定义了的 jsx element 必须使用
        // 'react/jsx-uses-vars': 2,
        // // @fixable 多行的 jsx 必须有括号包起来
        // // @off 没必要限制
        // 'react/jsx-wrap-multilines': 2,
        // "react/no-array-index-key": 2, // 遍历出来的节点必须加key
        // "react/no-children-prop": 2, // 禁止使用children作为prop
        // "react/no-direct-mutation-state": 2, // 禁止直接this.state = 方式修改state 必须使用setState
      },
    };

    webpack.common.js

    ...
    module: {
      rules: [
        ...
        {
          test: /\.(js|jsx)$/,
          loader: 'eslint-loader',
          enforce: 'pre',
          include: [path.resolve(__dirname, 'src')], // 指定检查的目录
          options: {
            // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
            formatter: require('eslint-friendly-formatter'), // 指定错误报告的格式规范
          },
        },
      ]
    }
    ...

    根目录下新建 .eslintignore 文件 用来制定忽略某些文件的 eslint 校验

    webpack

3. stylelint

    1. 下载依赖
    npm i stylelint stylelint-config-recommended stylelint-config-standard stylelint-order stylelint-webpack-plugin -D
    1. stylelint 配置

    webpack.dev.js

    const merge = require("webpack-merge");
    const StyleLintPlugin = require("stylelint-webpack-plugin");
    const common = require("./webpack.common.js");
    module.exports = merge(common, {
      mode: "development",
      devtool: "inline-source-map",
      devServer: {
        contentBase: "/src",
        hot: true
      },
      plugins: [
        new StyleLintPlugin({
          fix: true,
          files: ["src/**/*.scss"],
          failOnError: false,
          quiet: true,
          syntax: "scss",
          cache: true
        })
      ]
    });

    根目录下新建 .stylelintrc.js 文件

    module.exports = {
      extends: ['stylelint-config-standard', 'stylelint-config-recommended'],
      plugins: ['stylelint-order'],
      rules: {
        'order/order': [
          // "at-rules",
          // "declarations",
          'custom-properties',
          'dollar-variables',
          'rules',
        ],
        'order/properties-order': [
          'position',
          'z-index',
          'top',
          'bottom',
          'left',
          'right',
          'float',
          'clear',
          'columns',
          'columns-width',
          'columns-count',
          'column-rule',
          'column-rule-width',
          'column-rule-style',
          'column-rule-color',
          'column-fill',
          'column-span',
          'column-gap',
          'display',
          'grid',
          'grid-template-rows',
          'grid-template-columns',
          'grid-template-areas',
          'grid-auto-rows',
          'grid-auto-columns',
          'grid-auto-flow',
          'grid-column-gap',
          'grid-row-gap',
          'grid-template',
          'grid-template-rows',
          'grid-template-columns',
          'grid-template-areas',
          'grid-gap',
          'grid-row-gap',
          'grid-column-gap',
          'grid-area',
          'grid-row-start',
          'grid-row-end',
          'grid-column-start',
          'grid-column-end',
          'grid-column',
          'grid-column-start',
          'grid-column-end',
          'grid-row',
          'grid-row-start',
          'grid-row-end',
          'flex',
          'flex-grow',
          'flex-shrink',
          'flex-basis',
          'flex-flow',
          'flex-direction',
          'flex-wrap',
          'justify-content',
          'align-content',
          'align-items',
          'align-self',
          'order',
          'table-layout',
          'empty-cells',
          'caption-side',
          'border-collapse',
          'border-spacing',
          'list-style',
          'list-style-type',
          'list-style-position',
          'list-style-image',
          'ruby-align',
          'ruby-merge',
          'ruby-position',
          'box-sizing',
          'width',
          'min-width',
          'max-width',
          'height',
          'min-height',
          'max-height',
          'padding',
          'padding-top',
          'padding-right',
          'padding-bottom',
          'padding-left',
          'margin',
          'margin-top',
          'margin-right',
          'margin-bottom',
          'margin-left',
          'border',
          'border-width',
          'border-top-width',
          'border-right-width',
          'border-bottom-width',
          'border-left-width',
          'border-style',
          'border-top-style',
          'border-right-style',
          'border-bottom-style',
          'border-left-style',
          'border-color',
          'border-top-color',
          'border-right-color',
          'border-bottom-color',
          'border-left-color',
          'border-image',
          'border-image-source',
          'border-image-slice',
          'border-image-width',
          'border-image-outset',
          'border-image-repeat',
          'border-top',
          'border-top-width',
          'border-top-style',
          'border-top-color',
          'border-top',
          'border-right-width',
          'border-right-style',
          'border-right-color',
          'border-bottom',
          'border-bottom-width',
          'border-bottom-style',
          'border-bottom-color',
          'border-left',
          'border-left-width',
          'border-left-style',
          'border-left-color',
          'border-radius',
          'border-top-right-radius',
          'border-bottom-right-radius',
          'border-bottom-left-radius',
          'border-top-left-radius',
          'outline',
          'outline-width',
          'outline-color',
          'outline-style',
          'outline-offset',
          'overflow',
          'overflow-x',
          'overflow-y',
          'resize',
          'visibility',
          'font',
          'font-style',
          'font-variant',
          'font-weight',
          'font-stretch',
          'font-size',
          'font-family',
          'font-synthesis',
          'font-size-adjust',
          'font-kerning',
          'line-height',
          'text-align',
          'text-align-last',
          'vertical-align',
          'text-overflow',
          'text-justify',
          'text-transform',
          'text-indent',
          'text-emphasis',
          'text-emphasis-style',
          'text-emphasis-color',
          'text-emphasis-position',
          'text-decoration',
          'text-decoration-color',
          'text-decoration-style',
          'text-decoration-line',
          'text-underline-position',
          'text-shadow',
          'white-space',
          'overflow-wrap',
          'word-wrap',
          'word-break',
          'line-break',
          'hyphens',
          'letter-spacing',
          'word-spacing',
          'quotes',
          'tab-size',
          'orphans',
          'writing-mode',
          'text-combine-upright',
          'unicode-bidi',
          'text-orientation',
          'direction',
          'text-rendering',
          'font-feature-settings',
          'font-language-override',
          'image-rendering',
          'image-orientation',
          'image-resolution',
          'shape-image-threshold',
          'shape-outside',
          'shape-margin',
          'color',
          'background',
          'background-image',
          'background-position',
          'background-size',
          'background-repeat',
          'background-origin',
          'background-clip',
          'background-attachment',
          'background-color',
          'background-blend-mode',
          'isolation',
          'clip-path',
          'mask',
          'mask-image',
          'mask-mode',
          'mask-position',
          'mask-size',
          'mask-repeat',
          'mask-origin',
          'mask-clip',
          'mask-composite',
          'mask-type',
          'filter',
          'box-shadow',
          'opacity',
          'transform-style',
          'transform',
          'transform-box',
          'transform-origin',
          'perspective',
          'perspective-origin',
          'backface-visibility',
          'transition',
          'transition-property',
          'transition-duration',
          'transition-timing-function',
          'transition-delay',
          'animation',
          'animation-name',
          'animation-duration',
          'animation-timing-function',
          'animation-delay',
          'animation-iteration-count',
          'animation-direction',
          'animation-fill-mode',
          'animation-play-state',
          'scroll-behavior',
          'scroll-snap-type',
          'scroll-snap-destination',
          'scroll-snap-coordinate',
          'cursor',
          'touch-action',
          'caret-color',
          'ime-mode',
          'object-fit',
          'object-position',
          'content',
          'counter-reset',
          'counter-increment',
          'will-change',
          'pointer-events',
          'all',
          'page-break-before',
          'page-break-after',
          'page-break-inside',
          'widows',
        ],
        indentation: 'tab',
        'color-no-invalid-hex': true,
        'font-family-no-missing-generic-family-keyword': null,
        'font-family-name-quotes': null,
        'function-url-quotes': 'always',
        'at-rule-no-unknown': null,
        'no-eol-whitespace': null,
        'selector-attribute-quotes': 'always',
        'string-quotes': 'single',
        'selector-pseudo-element-colon-notation': null,
        'at-rule-no-vendor-prefix': true,
        'media-feature-name-no-vendor-prefix': null,
        'media-feature-name-no-unknown': null,
        'property-no-vendor-prefix': null,
        'selector-no-vendor-prefix': true,
        'value-no-vendor-prefix': true,
        'selector-pseudo-class-no-unknown': null,
        'shorthand-property-no-redundant-values': null,
        'at-rule-empty-line-before': null,
        'at-rule-name-space-after': null,
        'comment-empty-line-before': null,
        'declaration-bang-space-before': null,
        'declaration-empty-line-before': null,
        'function-comma-newline-after': null,
        'function-name-case': null,
        'function-parentheses-newline-inside': null,
        'function-max-empty-lines': null,
        'function-whitespace-after': null,
        'number-leading-zero': null,
        'number-no-trailing-zeros': null,
        'rule-empty-line-before': null,
        'selector-combinator-space-after': null,
        'selector-list-comma-newline-after': null,
        // "selector-pseudo-element-colon-notation": null,
        'unit-no-unknown': null,
        'no-descending-specificity': null,
        'value-list-max-empty-lines': null,
      },
    };

墨风
76 声望1 粉丝

« 上一篇
js生成树结构
下一篇 »
常用 CSS 布局