webpack4.X 实战(三):企业SPA 24点总结(上)

2,535 阅读8分钟

1. 区分 开发 / 生产环境 webpack-merge

  • 遵循不重复原则(DRY)

    webpack 的相关配置需要保留一个 common配置、一个dev配置、一个prod配置

    通过 webpack-merge 包将其整合

  • 开发环境时 一些工具的使用是没有意义的,比如 压缩代码、文件名哈希、分离代码等...

  • 项目中 安装、配置如下

    npm i webpack-merge@4.1.5 -D
    
    // webpack/webpack.common.js
    
    
    const path = require('path');
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: {
        app: './src/index.js'
      },
      plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
          title: 'Production'
        })
      ],
      output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
      }
    };
    
    // webpack/webpack.dev.js
    
    
    const merge = require('webpack-merge');
    const common = require('./webpack.common.js');
    
    module.exports = merge(common, {
      devtool: 'inline-source-map',
      devServer: {
        contentBase: './dist'
      }
    });
    
    // webpack/webpack.prod.js
    
    
    const merge = require('webpack-merge');
    const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
    const common = require('./webpack.common.js');
    
    module.exports = merge(common, {
      plugins: [
        new UglifyJSPlugin()
      ]
    });
    
    // package.json 配置 npm script
    
    {
      "scripts": {
        "start": "webpack-dev-server --open --config webpack/webpack.dev.js",
        "build": "webpack --config webpack/webpack.prod.js"
      }
    }
    

2. 配置 source map

  • source map 反应资源的映射关系,用于 定位代码中的错误

  • 开发环境下 建议

    {
        devtool: "cheap-module-eval-source-map"
    }
    
  • 生产环境下 建议

    {
        devtool: false
    }
    
  • 详细配置 见官方

3. 摇树优化 Tree Shaking

  • 作用:

    • 净化 JS 中无用的代码
  • 符合如下条件,自动开启 Tree Shaking:

    • webpack 4.X 生产模式下

    • 编码时,遵循 ES6 模块化语法(require 无效)

    • 编译时,不要编译 ES6模块

    // .babelrc 配置如下
    
    {
        "presets": [
            [
                "@babel/preset-env",
                {
                    "modules": false
                }
            ]
        ]
    }
    
  • 生产模式下 ES6 模块化语法 实践:

    • 实践一:

      // a.js 文件
      
      const dict = 'dict';
      const dictMedia = 'dictMedia';
      
      export default {
          dict,
          dictMedia
      };
      
      // 引入 a.js 文件
      
      import dicts from '/a';
      
      console.log(dicts);
      // a.js 文件中的 dict、dictMedia 都被打包   
      
    • 实践二:

      // a.js 文件
      
      const dict = 'dict';
      const dictMedia = 'dictMedia';
      
      export default {
          dict,
          dictMedia
      };
      
      // 引入 a.js 文件
      
      import dicts from '/a';
      
      console.log(dicts.dict);    
      // a.js 文件中的 dict 被打包;dictMedia 不被打包
      
    • 实践三:

      // a.js 文件
         
      const dict = 'dict';
      const dictMedia = 'dictMedia';
         
      export {
          dict,
          dictMedia
      };
      
      // 引入 a.js 文件
         
      import {dict, dictMedia} from '/a';
         
      console.log(dict, dictMedia);    
      // a.js 文件中的 dict 被打包;dictMedia 不被打包
      
    • 实践四:

      // a.js 文件
      
      const dict = 'dict';
      const dictMedia = 'dictMedia';
      
      export {
          dict,
          dictMedia
      };
      
      // 引入 a.js 文件
      
      import {dict} from '/a';
      
      console.log(dict);  
      // a.js 文件中的 dict 被打包;dictMedia 不被打包
      

4. 作用域提升 Scope Hoisting

  • 概述

    • Scope Hoisting 可以让webpack打包出来的代码文件更小、运行更快

    • webpack4 的生产模式,默认开启 Scope Hoisting

    • webpack3 推出的功能,需要手动开启 粗略讲解 在此

  • 原理:

    • 分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去

    • 前提是不能造成代码冗余

  • 是否开启 Scope Hoisting 的对比

    // util.js 文件
    
    export default 'Hello,Webpack';
    
    // main.js 入口文件
    
    import str from './util.js';
    console.log(str);
    
    // 未开启 Scope Hoisting 打包后如下
    
    [
      (function (module, __webpack_exports__, __webpack_require__) {
        var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1);
        console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"]);
      }),
      (function (module, __webpack_exports__, __webpack_require__) {
        __webpack_exports__["a"] = ('Hello,Webpack');
      })
    ]
    
    // 开启 Scope Hoisting 打包后如下
    
    [
      (function (module, __webpack_exports__, __webpack_require__) {
        var util = ('Hello,Webpack');
        console.log(util);
      })
    ]
    

5. 启动开发服务 热替换 HMR

  • 全称:Hot Module Replacement

  • 应用场景:开发环境下

  • 作用:热替换 HMR,在启动开发服务时,局部加载 页面被修改之处;加快开发编译速度

    保留在完全重新加载页面时丢失的应用程序状态

    只更新变更内容,以节省宝贵的开发时间

    调整样式更加快速:几乎相当于在浏览器调试器中更改样式

  • 限制:HMR 是可选功能(只会影响包含 HMR 代码的模块)

    举个例子,通过 style-loader 为 style 样式追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式

    如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着一个简单的处理函数能够对整个模块树(complete module tree)进行更新

  • 配置 HMR

    // webpack 配置文件中
    const webpack = require('webpack');
    
    
    module.exports = {
        devServer: {
          contentBase: path.resolve(__dirname,'dist'),
          compress: true,
          host: 'localhost',   
          port:3000,
          hot: true         // 开启 热替换
        },
    
        plugins: [
          new webpack.NamedModulesPlugin(),         // 必要的配置
          new webpack.HotModuleReplacementPlugin()  // 必要的配置
        ]
    };
    

6. 项目开发中 善用 按需加载

  • 方式一:使用 ES6 的模块化语法,动态加载模块 import()

    import('') 语法目前只是 ECMAScript 提案阶段,还没被正式发布

    详细见 import API

    // .babelrc 中
    // babel 7
    
    {
        "plugins": [
            "@babel/plugin-syntax-dynamic-import"
        ]
    }
    
  • 方式二:使用 webpack 对代码进行分割,按需加载(webpack 的 require.ensure 语法)

    • require.ensure 是 webpack 语法:
      • 参数1:要依赖的模块;类型为 字符串数组;一般为空

      • 参数2:加载依赖后,自动执行的回调函数

      • 参数3:打包后,js 文件的输出路径、js 文件名(chunk名称)

      const router = new VueRouter({
          routes: [               // 定义路由信息对象
              {
                  path: string,
                 
                  name?: string,    
                 
                  component: (resolve) => {
                      require.ensure([], () => {
                          resolve(require('../../view/demo/index.vue'))
                      }, 'demo')
                  }
                 
              }
          ]
      });
      

7. 配置 loader 生效范围

  • 作用:配置 loader 的生效范围,可提高编译速度

  • 以配置 babel-loader 为例

    // 不推荐
    // webpack配置文件中 配置
    const path = require('path');
    
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader']
        }]
    }
    
    // 推荐
    // webpack配置文件中 配置
    const path = require('path');
    
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader'],
            exclude: '', // 排除不要加载的文件夹
            include: [path.resolve(__dirname, 'src'), /node_modules/] // 指定需要加载的文件夹
        }]
    }
    
  • excludeinclude 的值:

    • 值可以是单独项、可以是数组

    • 可以是路径、可以是正则

  • 项目实战:

    项目开发中,针对主要 lodaer 如 babel-loaderstyle-loadersass-lodaer

    配置生效范围: 除了要转换项目代码,还要转换 node_modules 中代码;否则 针对没有完全转成JS的node包,会报错

8. 配置 如何解析模块 resolve

  • 给模块起别名:resolve.alias

    // webpack 配置
    module.exports = {
      //...
      resolve: {
        alias: {
            '@components': '/src/common'
        }
      }
    };
    
    // 项目代码
    
    import a from '@components/utils';
    
    // 等同于
    import a from '/src/common/utils';
    
  • 自动添加后缀 规则:resolve.extensions

    当引入模块时不带文件后缀,webpack 会根据配置依次添加后缀 寻找文件

    // webpack 配置
    module.exports = {
      //...
      resolve: {
        extensions: ['.vue', '.js', '.scss', '.css', '.json']
      }
    };
    
  • 解析目录时要使用的文件名:resolve.mainFiles

    默认寻找 index 命名的文件

    // webpack 配置
    module.exports = {
      //...
      resolve: {
        mainFiles: ["index"]
      }
    };
    
  • 指明第三方模块存放的位置,以减少搜索步骤

    • 默认值:[node modules]

    • 默认搜索步骤:先去当前目录的 /node modules 目录下去找我们想找的模块,如果没找到,就去上一级目录 ../node modules 中找,再没有就去 ../../node modules 中找

     // webpack 配置
    module.exports = {
      //...
      resolve: {
        modules: [path.resolve(__dirname, 'node_modules')]
      }
    };
    
  • 引入的模块,寻找规则:

    不建议写文件后缀名,就意味着引入的模块路径,路径最后一层有可能是文件;有可能是文件夹

    默认将路径的最后一层 视为文件名,依次匹配 配置的后缀名

    如果没找到该文件,将路径的最后一层视为文件夹,依次匹配 设置的文件名,找到后再 依次匹配设置的后缀名

    如都没找到,就会报错

  • resolve 详细配置 见官网

9. 配置 noParse 让webpack编译时 忽略一些文件

  • 场景:

    • 一些没有采用模块化的文件,没有必要让 webpack 进行处理编译

    • 通过配置 modules.noParse,可以让 webpack 忽略,提升编译速度

  • 配置:

    module.exports = merge(common, {
        modules: {
            // 使用正则表达式
            noParse: /jquery|chartjs/
           
            // 或
           
            // 使用函数,从 Webpack 3.0.0 开始支持
            noParse: (content)=> {
              // content 代表一个模块的文件路径
              // 返回 true or false
              return /jquery|chartjs/.test(content);
            }
        }
    });
    

10. 配置 performance 在生产环境 移除性能警告

  • webpack 打包后,如果文件体积超出默认(250kb)大小,会输出警告

  • 开发环境下这是一个不错的提示,但生产环境下完全没必要,可以通过 performance 进行相关配置

  • 配置如下

    详细配置 见官网

    // webpack 配置
    
    module.exports = merge(common, {
        performance: {
            hints: false,               // 关闭警告
            maxEntrypointSize: 400000   // 预警值设置成40kb
        }
    });
    

11. 配置 项目环境变量 webpack.DefinePlugin

  • 场景:项目开发过程中,配置 每个文件中都可以使用的 JS 变量

  • 直接获取 开发环境变量 process.env.NODE_ENV

    使用webpack4.x,webpack 会将环境变量 process.env.NODE_ENV 的值,设置为 webpack配置中 mode 的值

    在项目代码中 可以直接使用 process.env.NODE_ENV

    // 项目代码中
    
    console.log(process.env.NODE_ENV);  // development
    
  • 设置 / 获取 新的环境变量

    使用 webpack 的内置插件 DefinePlugin,可以为项目代码定义环境变量

    限制:DefinePlugin 设置的环境变量只能在项目代码中获取,不能再 webpack 的配置文件中获取

    // webpack 配置如下
       
    const webpack = require('webpack');
       
    module.exports = merge(common, {
        plugins: [
            new webpack.DefinePlugin({
                'process.env.NODE_ENV': JSON.stringify('ddd'),      // 覆盖 process.env.NODE_ENV
                'process.env.globalName': JSON.stringify('globalName'),
                'globalName': JSON.stringify('eeee'),
    
            })
        ]
    });
    
    // 项目代码中
    
    console.log(process.env.NODE_ENV);      // ddd
    console.log(process.env.globalName);    // globalName
    console.log(globalName);                // eeee
    

12. npm script 指令传参(三种方式)

  • 场景:配置webpack时,可能需要通过 npm script 传参,用来处理不同场景下的不同需求

  • 方式一:不同系统存在 兼容问题

    • 在脚本命令的配置(package.json 的 script 下) 中传参(window)

      // window 系统下:传参
      
      "scripts": {
          "server": "webpack-dev-server --open",
          "build:dev":"set type=dev&webapck",
          "build:prod": "set type=prod&webpack"
      },
      
    • 在脚本命令的配置(package.json 的 script 下) 中传参(mac)

      // mac 系统下:传参
      
      "scripts": {
          "server": "webpack-dev-server --open",
          "build:dev":"export type=dev&&webpack",
          "build:prod": "export type=prod&&webpack"
      },
      
    • 接收参数

      // node的语法来读取type的值,然后根据type的值用if–else判断
      
      if(process.env.type== "build"){
          // 生产环境
          var website={
              publicPath:"http://192.168.0.104:1717/"
          }
      }else{
          // 开发环境
          var website={
              publicPath:"http://cdn.jspang.com/"
          }
      }
      
  • 方式二:要求 webpack 配置项输入函数(无系统兼容问题)

    • 在脚本命令的配置(package.json 的 script 下) 中传参

      // package.json 如下
         
      {
          "scripts": {
              "build": "webpack --env.NODE_ENV=local --env.production --progress"
          },
      }
      
    • webpack 配置文件中 获取环境变量

      必须对 webpack 配置进行一处修改。通常,module.exports 指向配置对象;要使用 env 变量,你必须将 module.exports 转换成一个函数

      module.exports = env => {
        console.log('NODE_ENV: ', env.NODE_ENV) // 'local'
        console.log('Production: ', env.production) // true
      
        return {
          entry: './src/index.js',
          output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
          }
        }
      };
      
    • 执行命令 npm run build,即可在控制台中 看到输出的 环境变量的值

      这样就可以在 webpack 中通过区分不同的环境变量,来配置不同的webpack

  • 方式三:无系统兼容问题,无过多要求

    • 在脚本命令的配置(package.json 的 script 下) 中传参

      // package.json 如下
         
      {
          "scripts": {
              "build": "webpack --prod"
          },
      }
      
    • webpack 配置文件中 获取参数如下

      // webpack 配置文件中
      
      console.log(process.argv);
      // 输出 [ node 路径, webpack 路径, '--prod']
      
    • 如果只是想传递一个布尔值,获取参数如下

      // 安装 minimist
      
      npm i minimist@1.2.0 -D
      
      // webpack 配置文件中
      const processArgv = require('minimist')(process.argv.slice(2));
      
      
      console.log(processArgv.prod);      // true