美文网首页
前端工程化(三)

前端工程化(三)

作者: 望月从良glh | 来源:发表于2020-07-18 22:37 被阅读0次

    webpack 打包

    模块化开发为我们解决了很多问题,使得代码组织管理非常的方便,但是又带来了新的问题,ES Module 存在环境兼容问题,划分的文件太多,就会导致网络请求频繁,不能保证所有资源的模块化

    如果能我们享受模块化带来的开发优势,又能不必担心生产环境的存在这些问题,于是就有了 webpack, rollup, Parcel 等工具
    webpack 模块化不等于 js ES modele 模块,相对来讲是前端的模块化处理方案,更加宏观

    • 快速上手
    $ yarn init --yes
    $ yarn add webpack webpack-cli -D
    $ yarn webpack --version
    $ yarn webpack // 默认打包src/index.js // 最终存放到dist/main.js
    
    • webpack 配置文件

    在项目根目录添加 webpack.config.js

    const path = require('path');
    
    module.exports = {
      entry: './src/main.js', // 入口文件
      output: {
        filename: 'bundle.js', // 输出文件名
        path: path.join(__dirname, 'output'), // 输出文件路径(绝对路径)
      },
    };
    
    • 工作模式

    webpack4 新增了工作模式的用法,大大简化了配置的复杂程度;三种工作模式 mode: production development none

    $ webpack --mode none
    $ webpack --mode production // 默认模式
    $ webpack --mode development
    

    或者采用配置的方式

    const path = require('path');
    
    module.exports = {
      // 这个属性有三种取值,分别是 production、development 和 none。
      // 1. 生产模式下,Webpack 会自动优化打包结果;
      // 2. 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;
      // 3. None 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;
      mode: 'development',
      entry: './src/main.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
      },
    };
    
    • 资源模块加载

      webpack 内部的 loader 只能处理 js 文件,其他文件我们需要配置对应的 loader 才可以完成打包,否则会报错。

    const path = require('path');
    
    module.exports = {
      mode: 'none',
      entry: './src/main.css',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
      },
      module: {
        rules: [
          {
            test: /.css$/,
            // css-loader作用就是将css代码转化为js模块
            // style-loader作用就是将cssloader转化的结果追加到页面
            use: ['style-loader', 'css-loader'],
          },
        ],
      },
    };
    
    • 导入资源模块
      入口文件为 js 文件,根据代码的需要动态导入其他资源,由 javascript 驱动整个前端应用
    const path = require('path');
    
    module.exports = {
      mode: 'none',
      entry: './src/main.css',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
      },
      module: {
        rules: [
          {
            test: /.css$/,
            use: ['style-loader', 'css-loader'],
          },
        ],
      },
    };
    
    // main.js
    import './main.css';
    
    • 文件资源加载器

      • file-loader
        经过 file-loader 处理后,将文件资源放到我们打包目录的根目录。返回文件资源的访问路径,通过 import 就可以拿到文件资源的路径。webpack 默认认为文件资源放在网站的根目录下
        会发起文件请求
    const path = require('path');
    
    module.exports = {
      mode: 'none',
      entry: './src/main.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/',
      },
      module: {
        rules: [
          {
            test: /.css$/,
            use: ['style-loader', 'css-loader'],
          },
          {
            test: /.png$/,
            use: 'file-loader',
          },
        ],
      },
    };
    
    // main.js
    import createHeading from './heading.js';
    import './main.css';
    import iconURL from './icon.png';
    // 经过file-loader处理后,将图片放到我们打包目录的根目录。返回图片的访问路径,通过import就可以拿到图片的路径。webpack默认认为图片放在网站的根目录下
    const heading = createHeading();
    
    document.body.append(heading);
    
    const img = new Image();
    img.src = iconURL;
    
    document.body.append(img);
    
    • url-loader
      将文件资源转化为 Data Url, 最终返回这个 Data Url,不单独生成资源文件,直接嵌入到 bundle.js 中
      当资源文件过大时,导致 base64 边长,打包的 bundle.js 体积过大

      • Data URLs
        直接表示文件内容,使用这种 Url 不会发起 Http 请求

        data:[<mediatype>][;base64],<data>
        
        // 协议 + 媒体类型以及编码+ 文件内容(图片会被转化为base64)
        

    最佳实践:小文件使用 Data URLs, 减少请求次数。大文件单独提取,避免 bundle.js 过大,加载时间过长

    module: {
      rules: [
        {
          test: /.css$/,
          use: ['style-loader', 'css-loader'],
        },
        {
          test: /.png$/,
          use: {
            // 必须同时安装file-loader,当超过limit设置的值,url-loader会自动让file-loader处理
            loader: 'url-loader',
            options: {
              limit: 10 * 1024, // 10 KB
            },
          },
        },
      ];
    }
    
    • 常用 loader 分类

      • 编译转化类型

      • 文件操作类型

      • 代码质量检查

    • 处理 ES6+新特性

      webpack 只是打包工具 默认处理代码中的 export 和 import,但对其他 ES6+新特性不做处理,需要 babel-loader

    $ yarn add babel-loader @babel/core @babel/preset-env -D
    
    // babel 只是一个js的转换平台。基于平台通过不同的插件实现转化
    
    {
      "test": /.js$/,
      "use": {
        "loader": "babel-loader",
        "options": {
          "presets": ["@babel/preset-env"]
        }
      }
    }
    
    • 模块加载方式
      webpack 兼容多种标准的模块加载方式

      • ES Module
      • CommonJs
      • AMD
      • import('XXX.css')
      • @import ()
      • @import url()
      • html 中的 img 的 src 属性
      • background 属性的 url
      • a 标签的 herf 属性
        ...
    • webpack 核心工作原理

      • 首先设置入口文件,webpack 会根据配置找到入口文件(如果不设置默认 src 下面的 index.js 文件)作为我们的打包入口

      • 根据代码中出现的 import 或者 require 解析推断出这个文件所依赖的其他资源模块

      • 然后分别延伸解析每一个资源模块对应的依赖,形成一个整个项目当中所有用的资源文件的依赖树

      • 然后递归这个依赖树,找到每个节点对应的资源文件,根据配置文件的 rules 属性找到当前模块的加载器(loader),然后交给加载器加载这个模块

      • 最终将执行完成的结果放到 output 对应的 bundle.js 中

      • 在整个过程中,会通过 webpack 提供的钩子函数(生命周期函数)加载对应的任务。这个任务我们也成 plugins

    • webpack 开发一个 loader
      原则: 对同一文件所用到的 loader 执行完成后, 最终必须返回 javascropt 代码,也就是处理当前资源的最后的 loader 必须是返回 javascript 代码

    const path = require('path');
    
    module.exports = {
      mode: 'none',
      entry: './src/main.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/',
      },
      module: {
        rules: [
          {
            test: /.md$/,
            // 将 md 转化为 html
            use: ['html-loader', './markdown-loader'],
          },
        ],
      },
    };
    
    // main.js
    import about from './about.md';
    
    console.log(about);
    
    // markdown-loader.js
    const marked = require('marked');
    
    module.exports = source => {
      // source为加载进来的资源内容
    
      const html = marked(source);
      // 如果不交给下个loader处理
    
      // return `module.exports = "${html}"`
      // return `export default ${JSON.stringify(html)}`
      // 如果交给下个loader处理
      // 返回 html 字符串交给下一个 loader 处理
      return html;
    };
    
    • 常用插件 Plugin

      clean-webpack-plugin
      每次打包前先清除 webpack 输出目录

      HtmlWebpackPlugin
      每次打包的文件自动生成 html 文件,自动引入打包结果

    plugins: [
      new webpack.ProgressPlugin(),
      new CleanWebpackPlugin(),
      // 不额外添加模板的使用
      new HtmlWebpackPlugin({
        title: 'glh', // 设置标题
        meta: {
          // 设置meta标签
          viewport: 'width=device-width',
        },
        // ...
      }),
    ];
    
    // 添加模板,让HtmlWebpackPlugin根据模板生成
    new HtmlWebpackPlugin({
      title: 'glh', // 设置标题
      meta: {
        // 设置meta标签
        viewport: 'width=device-width',
      },
      template: './public/index.html',
      templateParameters: {
        // 自定义变量
        BASE_URL: './',
      },
      // ...
    });
    
    <!--  public/index.html -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
        <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
        <title><%= htmlWebpackPlugin.options.title %></title>
      </head>
    
      <body>
        <noscript>
          <strong
            >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
            properly without JavaScript enabled. Please enable it to
            continue.</strong
          >
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>
    
    // 用于生成index.html
    new HtmlWebpackPlugin({
      template: './public/index.html',
      // ...
    });
    // 用于生成about.html
    new HtmlWebpackPlugin({
      filename: 'about.html',
      // ...
    });
    

    copy-webpack-plugin
    对一些公共资源文件直接复制到打包目录中。比如 public/favicon.ico

    new CopyWebpackPlugin({
      patterns: [{ from: 'public/favicon.ico', to: '.' }],
    });
    

    我们一般在使用插件的时候掌握一些经常用的就可以。后面根据需求再去提炼关键词,搜索自己想用的插件,当然也可以自己写。插件的约定名称一般都是 XXX-webpack-plugin,比如我们想要压缩图片就可以找 imagemin-webpack-plugin

    • 实现一个自定义 plugin

    首先要明白:

    • Plugin 其实就是通过在生命周期的钩子中挂载函数实现扩展。类似于我们 React 中的声明周期。
      webpack 在工作的过程中给每一个环节都埋下了钩子,我们只需要在对应的钩子下挂载任务就可以轻松的扩展 webpack 的能力

    自定义的 Plugin 其实就是一个函数,或者包含 apply 的方法的对象
    apply 方法接受一个 compiler 对象参数,这个参数包含我们整个构建过程中的所有配置信息,通过这个对象我们可以注册钩子函数,通过 tap 方法注册任务
    tap 方法又接受两个参数,一个是插件名称,一个是当前次打包执行的上下文

    • 和 loader 区别:loader 是专注实现资源模块加载转化
      Plugin 是解决处理资源加载转化之外的的一些自动化工作
      相比于 Loader,Plugin 的能力范围更宽
      因为 Loader 只是在加载模块的范围工作,而插件的工作范围可以触及到 webpack 的每一个环节
    class MyPlugin {
      apply(compiler) {
        console.log('MyPlugin 启动');
        // 这里要做的事情就是在emit钩子上挂载一个任务,这个任务帮我们去除打包后没有必要的注释(mode=none情况下)其他钩子可参考官网
        compiler.hooks.emit.tap('MyPlugin', compilation => {
          // compilation => 可以理解为此次打包的上下文
          for (const name in compilation.assets) {
            // console.log(name)
            // console.log(compilation.assets[name].source())
            if (name.endsWith('.js')) {
              const contents = compilation.assets[name].source();
              const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
              compilation.assets[name] = {
                source: () => withoutComments,
                size: () => withoutComments.length,
              };
            }
          }
        });
      }
    }
    plugins: [new MyPlugin()];
    
    • 增强 webpack 的开发体验
    // 不使用Webpack Dev Server情况下,自动监听打包文件的变化
    $ yarn webpack --watch
    $ http-server -c-1 dist //or $ browser-sync dist --file  "**/*"
    

    以上方式效率太低,文件不断的被读写操作,有待优化

    • Webpack Dev Server

      编写源代码=> webpack 打包=> 运行应用=> 刷新浏览器
      我们可以借助 Webpack Dev Server 来提升开发体验,更接近生产环境的运行状态,同时也可以设置 proxy,对于错误我们可以使用 souceMap 来快速定位源代码问题

    $ yarn add webpack-dev-server -D
    $ yarm webpack-dev-server --open
    

    webpack-dev-server 并不会将打包结果放到磁盘中,暂时存放到内存中,从临时内存中读取内容发送给浏览器,从而大大提高了效率

    • webpackDevServer 的静态资源访问
    devServer: {
      contentBase: './public', //也可以指定数组标识多个目录
    }
    
    • 代理 proxy
      代理方式适用于后端没有配置 cors 的情况
      如果我们的项目最终上线前后端代码符合同源策略,也就没必要设置 cors 了,这个时候可以通过本地服务器配置代理的方式实现跨域请求
      devServer: {
        proxy: {
          '/api': {
            // http://localhost:8080/api/users -> https://api.github.com/api/users
            target: 'https://api.github.com',
            // http://localhost:8080/api/users -> https://api.github.com/users
            pathRewrite: {
              '^/api': '' // 根据后端接口文件路劲因情况而定,这里只是用github举例说明
            },
            // 不能使用 localhost:8080 作为请求 GitHub 的主机名
            changeOrigin: true
          }
        }
      }
    
    // main.js;
    // 跨域请求,虽然 GitHub 支持 CORS,但是不是每个服务端都应该支持。
    // fetch('https://api.github.com/users')
    fetch('/api/users') // http://localhost:8080/api/users
      .then(res => res.json())
      .then(data => {
        data.forEach(item => {
          const li = document.createElement('li');
          li.textContent = item.login;
          ul.append(li);
        });
      });
    
    • sourceMap

      由于编写的代码和运行的代码不一致,sourceMap 帮我们定位源代码错误
      webpack 提供了 12 中 sourceMap 方式。每种方式的效果和效率不同,效果最好的,效率最差,效果最差的,效率最高,因此我们只需要实际开发中符合需求的最佳实践
      cheap: 定位到行,不定位列
      eval: 定位到文件
      module: 定位 loader 处理前的源代码
      inline: 把 sourcemap 嵌入到打包文件中,不额外生成对应的.map 文件
      hidden: 会有错误信息,但是不是源文件。开发第三方包的时候可以用
      nosources: 看不到源代码,但是会有行列信息,保护在生产环境中源代码不被暴露

    devtool: // 开发环境  'cheap-module-eval-source-map',
      // 生产环境 'none',
      // 如果对自己上线代码没有信心 'nosources-source-map'
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const allModes = [
      'eval',
      'cheap-eval-source-map',
      'cheap-module-eval-source-map',
      'eval-source-map',
      'cheap-source-map',
      'cheap-module-source-map',
      'inline-cheap-source-map',
      'inline-cheap-module-source-map',
      'source-map',
      'inline-source-map',
      'hidden-source-map',
      'nosources-source-map',
    ];
    
    module.exports = allModes.map(item => {
      return {
        devtool: item,
        mode: 'none',
        entry: './src/main.js',
        output: {
          filename: `js/${item}.js`,
        },
        module: {
          rules: [
            {
              test: /\.js$/,
              use: {
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-env'],
                },
              },
            },
          ],
        },
        plugins: [
          new HtmlWebpackPlugin({
            filename: `${item}.html`,
          }),
        ],
      };
    });
    
    • 热更新(HMR)代替自动刷新
      自动刷新导致页面状态丢失
      热更新就是在页面不跟新的情况下,只将修改的模块实时替换到应用中
    $ yarn webpack-dev-server --hot
    
    const webpack = require('webpack');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      mode: 'development',
      entry: './src/main.js',
      output: {
        filename: 'js/bundle.js',
      },
      devtool: 'source-map',
      devServer: {
        hot: true,
        // hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
      },
      plugins: [
        new webpack.HotModuleReplacementPlugin(),
        // ...
      ],
    };
    

    默认的 HMR 开启后还需要我们手动去处理热更新的逻辑。当然在 css 文件中由于 cssloader 中已经帮我们处理了,所以我们可以看到修改 css 可以出发热跟新
    编写的 js 模块由于代码太过灵活,如果没有框架的约束,wabpack 很难实现通用的热更新

    • HMR API
    import createEditor from './editor';
    import background from './better.png';
    import './global.css';
    
    const editor = createEditor();
    document.body.appendChild(editor);
    
    const img = new Image();
    img.src = background;
    document.body.appendChild(img);
    
    // ============ 以下用于处理 HMR,与业务代码无关 ============
    
    // console.log(createEditor)
    
    if (module.hot) {
      let lastEditor = editor;
      // 处理js模块的热更新
      module.hot.accept('./editor', () => {
        // console.log('editor 模块更新了,需要这里手动处理热替换逻辑')
        // console.log(createEditor)
    
        const value = lastEditor.innerHTML;
        document.body.removeChild(lastEditor);
        const newEditor = createEditor();
        // 解决文本框状态丢失
        newEditor.innerHTML = value;
        document.body.appendChild(newEditor);
        lastEditor = newEditor;
      });
      // 处理img热更新
      module.hot.accept('./better.png', () => {
        img.src = background;
        console.log(background);
      });
    }
    

    以上例子 只是说明 webpack 没办法提供通用方案。实现一个热更新原理就是利用 module.hot,HotModuleReplacementPluginApi 提供的这个。大部分框架中都集成了 HMR。

    • 不同环境的配置文件
    // 函数方式配置
    const webpack = require('webpack');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    
    module.exports = (env, argv) => {
      const config = {
        // ...
      };
    
      if (env === 'production') {
        config.mode = 'production';
        config.devtool = false;
        config.plugins = [
          ...config.plugins,
          new CleanWebpackPlugin(),
          new CopyWebpackPlugin(['public']),
        ];
      }
    
      return config;
    };
    

    文件划分的配置

    // webpack.common.js
    
    module.exports = {};
    
    // webpack.dev.js
    const common = require('./webpack.common');
    const merge = require('webpack-merge'); // 安装webpack-merge合并配置
    module.exports = merge(common, {
      mode: 'development',
      devtool: 'cheap-eval-module-source-map',
      devServer: {
        hot: true,
        contentBase: 'public',
      },
      plugins: [new webpack.HotModuleReplacementPlugin()],
    });
    
    // webpack.prod.js
    const merge = require('webpack-merge');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    const common = require('./webpack.common');
    
    module.exports = merge(common, {
      mode: 'production',
      plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(['public'])],
    });
    
    $ yarn webpack --config webpack.prod.js
    $ yarn webpack-dev-server --config webpack.dev.js
    
    • DefinePlugin
      为代码注入全局成员
      默认注入 process.evn.NODE_ENV 常量
    plugins: [
      new webpack.DefinePlugin({
        // 值要求的是一个代码片段
        API_BASE_URL: JSON.stringify('https://api.example.com'),
      }),
    ];
    
    • Tree-shaking
      将未引用代码去除掉 生产环境下自动开启
      在其他模式下开启需要:
     optimization: {
        // 模块只导出被使用的成员
        usedExports: true,
        // 尽可能合并每一个模块到一个函数中
        concatenateModules: true, // scope Hoisting
        // 压缩输出结果
        minimize: true
      }
    
    • Tree-shaking && babel
      由于 Tree-shaking 是基于 ESModule 实现的,但是 旧版本 babel 中如果用到 preset-env 的插件集合的时候会默认开启转化 ESModule 的导入导出语法为 Commonjs 的规范。所以导致 Tree-shaking 失效,新版本已默认关闭

    • sideEffects 新特新
      标识代码是否有副作用,为 Tree-shaking 提供更大的压缩空间

    // webpack.config.js
    optimization: {
      sideEffects: true; // 开启sideEffects功能
    }
    // package.json
    "siedEffects": false // 标识代码是否有副作用
    

    副作用需要我们手动添加并且谨慎使用,一般用在开发第三方包中,当我们的代码有副作用,但是却配置了以上两个属性,就会导致程序报错。

    // package.json 配置有副作用的文件,这样webpack在打包的过程中就不会忽略这些
    "siedEffects" :[
      "./src/extend.js",
      "*/css"
    ]
    
    • Code Splitting
      打包成一个文件导致体积过大,加载时间过长。
      应用启动的首屏并不是所有模块都工作的
      所以我们需要分包,按需加载

      • 多入口打包
        适用于多页面应用
        entry: {
          index: './src/index.js',
          album: './src/album.js'
        },
        output: {
          filename: '[name].bundle.js'
        },
        optimization: {
          splitChunks: {
          // 自动提取所有公共模块到单独 bundle
            chunks: 'all'
          }
        },
        plugins: [
          new HtmlWebpackPlugin({
            title: '首页',
            template: './src/index.html',
            filename: 'index.html',
            chunks: ['index'] //  对不同的页面指定不同的打包js文件
          }),
          new HtmlWebpackPlugin({
            title: '其他页面',
            template: './src/album.html',
            filename: 'album.html',
            chunks: ['album']
          })
        ]
      
      • 动态导入 import()
        适用于单页面应用
        在 react 或者 vue 中一般都是通过路由映射组件实现动态加载
        webpack 会根据 import()把对应的模块拆分到不同的输出文件,根据加载的需要执行不同的 js 文件
      // import posts from './posts/posts'
      // import album from './album/album'
      const render = () => {
      const hash = window.location.hash || '#posts'
      
      const mainElement = document.querySelector('.main')
      
      mainElement.innerHTML = ''
      
      if (hash === '#posts') {
          // mainElement.appendChild(posts())
          import(/_ webpackChunkName: 'components' _/'./posts/posts').then(({ default: posts }) => {
          mainElement.appendChild(posts())
        })
      } else if (hash === '#album') {
          // mainElement.appendChild(album())
          import(/_ webpackChunkName: 'components' _/'./album/album').then(({ default: album }) => {
          mainElement.appendChild(album())
          })
        }
      }
      
      render()
      
      window.addEventListener('hashchange', render)
      
    • 魔法注释
      通过在 import 语句中加注释的方式为 webpack 提供打包后的名称如果设置一样则打包到一个文件

    if (hash === '#posts') {
      // mainElement.appendChild(posts())
      import(/* webpackChunkName: 'components' */ './posts/posts').then(
        ({ default: posts }) => {
          mainElement.appendChild(posts());
        }
      );
    } else if (hash === '#album') {
      // mainElement.appendChild(album())
      import(/* webpackChunkName: 'components' */ './album/album').then(
        ({ default: album }) => {
          mainElement.appendChild(album());
        }
      );
    }
    
    • MiniCssExtractPlugin
      提取 css 到单个文件
      需要考虑 css 大小,如果很少写 css 那么还是采用 stypeLoader 注入到页面的 style 标签中
     module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              // 'style-loader', // 将样式通过 style 标签注入
              MiniCssExtractPlugin.loader, // 将样式通过Link标签方式注入
              'css-loader'
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin()
      ]
    
    • webpack 内部提供的生产环境的压缩只是针对 JS 代码的。如果想要压缩其他形式资源,需要单独安装对应的插件
    optimization: {
      minimize: [
      // 要使用其他压缩,这里要把默认的js压缩的插件也安装进来,是因为webpack会覆盖了 optimization原有的默认配置
      // 这里配置的压缩都只会在生产环境起作用,符合我们的预期,不用再去放到webpack.prod.js或者根据环境变量判断
      new TreserWebpackPlugin(),
      // 这里以压缩css为例,其他的参见官网
      new OptimizeCssAssetsWebpackPlugin()]
    }
    
    • 输出文件名称 Hash
      一般我们部署前端资源文件的时候,都会开启静态资源缓存,避免每次都请求资源,整体应用的响应速度就会大幅度提升,不过也会有问题,当我们缓存时间设置过长,我们的应用更新过后,浏览器并不会及时更新。这就需要我们在生产环境中需要给文件添加 Hash 值,全新的文件名就是全新的请求,也就避免了上述问题
      hash: 只要内容修改,所有文件 hash 都会跟新
      contenthash: 文件级别的 hash,当前修改的文件以及被引用的文件 hash 会被动跟新
      chunk: 当文件内容改变,只修改当前同类的 hash 值
      output: {
        filename: '[name]-[contenthash:8].bundle.js'
      },
    

    还有一些其他的配置项比如 preformance target externals resolve other option 我们只需要查阅官方文档即可,另外还需要多理解 manifest 和 runtime 这样的 webpack 概念

    相关文章

      网友评论

          本文标题:前端工程化(三)

          本文链接:https://www.haomeiwen.com/subject/iukqkktx.html