webpack各版本之间的差异

337 阅读6分钟

v1 -- v2

  • 新特性

    • 原生支持 ES6 模块

    • 可以混用 ES2015 和 AMD 和 CommonJS

    • 借鉴 rollup,支持 tree-shaking(减少打包后的体积)

      {
        "presets": 
        [   ["es2015", {"modules": false}]
      }
      optimization: {   
        // 模块只导出被使用的成员   
        usedExports: true,   
        // 压缩输出结果   
        minimize: true,   
        // 移除副作用代码   
        sideEffects: true
      }
      
  • 新增更多的 CLI 参数项 -p 指定当前的编译环境为生产环境,即设置 process.env.NODE_ENV 为 production

  • 差异

    • resolve 配置

    • extensions:取消了空字符串(表示导入文件无后缀名)

    • 指定依赖模块的查找路径由 modulesDirectories 改成 modules

    • module 配置

    • 外层 loaders 改为 rules

    • 内层 loader 改为 use

    • 所有插件必须加上 -loader,不再允许缩写

    • 不再支持使用!连接插件,改为数组形式

    • json-loader 模块移除,不再需要手动添加,webpack2 会自动处理

    • plugins 配置

    • 移除 OccurenceOrderPlugin 跟 NoErrorsPlugin ,改为内置

      module.exports = {entry: './src/index.js',output:{   filename:'bundle.js',   path:path.resolve(__dirname,'dist')},resolve: {   
        // webpack 1.x   
      extensions: ['', 'js', 'json'],   
      // webpack 2.x   
      extensions: ['js', 'vue', 'json']  
      // webpack 1.x   
      modulesDirectories: ["node_modules"],   
      // webpack 2.x   
      modules: [path.resolve(__dirname, 'node_modules')]},module: {   
        // webpack 1.x   
        loaders:[      
          {         
            test:/\.css$/,         
            loader: "style!css!less"      }   ],   
            // webpack 2.x   
            rules:[      
              {         
                test:/\.css$/,         
                use:['style-loader','css-loader'         
            ]}]}}
      

v2 -- v3

  • 新特性

    • Scope Hoisting

    • 使用 ModuleConcatenationPlugin 插件来启用作用域提升

      • 代码体积更小,因为函数申明语句会产生大量代码

      • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小

        const path = require('path')
        const {   CleanWebpackPlugin} = require('clean-webpack-plugin')
        const webpack = require('webpack')
        module.exports = {   
          mode: 'none',   
          entry: './src/index.js',   
          output: 
          {      
            filename: 'bundle.js',      
            path: path.resolve(__dirname, 'dist')   },   
            plugins: [      
              new CleanWebpackPlugin(),      // scope hoisting      
              new webpack.optimize.ModuleConcatenationPlugin()   
              ]}
        
  • Magic Comments

    • 可以分割代码块,为生成的 chunk 指定 chunkName 并实现懒加载

      const path = require('path')
      const {   CleanWebpackPlugin} = require('clean-webpack-plugin')
      const webpack = require('webpack')
      module.exports = 
      {   
        mode: 'none',   
        entry: './src/index.js',   
        output: {
          filename: 'bundle.js',
          path: path.resolve(__dirname, 'dist')
        },
        plugins: [new CleanWebpackPlugin(),
        // scope hoisting
        new webpack.optimize.ModuleConcatenationPlugin()      
        // 分包的chunk id 转为字符串标识符     
        new webpack.NamedChunksPlugin()]}
      

v3 -- v4

  • 新特性
    • 自动设置 process.env.NODE_EVN 到不同环境
    • development --> new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") })
    • production new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") })
    • mode (对不同的环境他会启用不同的配置)
    • none 去掉所有默认设置
    • development
      • new webpack.NamedModulesPlugin() 这个插件的作用是在热加载时直接返回更新文件名,而不是文件的 id,给各个模块加上姓名,便于开发的时候查找
      • new webpack.NamedChunksPlugin() 把 chunk id 变为一个字符串标识符
    • production 默认启用:
      • 混淆&压缩 JS
      • Scope Hoisting ModuleConcatenationPlugin
      • 通过 NoEmitOnErrorsPlugin 在编译出现错误时,直接跳过输出阶段。这样可以确保输出资源不会包含错误
      • Code Splitting splitChunks 取代 CommonsChunkPlugin
      • flagIncludedChunks 在编译输出 chunks 之间的引用模块的 id,便于直观各代码块之间的关系
      • SideEffectsFlagPlugin 去除副作用代码
  • 差异
    • 分包
    • optimization.splitChunks 取代 CommonsChunkPlugin
    • 压缩/混淆
    • optimization.minimize 为 true ( prod 默认置为 true) 取代 UglifyJsPlugin

v4 -- v5

  • 弃用的配置

    • v4 中不支持的所有项在 v5 中都被删除了,例如 extract-text-webpack-plugin
  • 新特性

    • 优化持久缓存

    • Webpack 在执行的时候,以配置的 entry 为入口,递归解析文件依赖,构建一个 graph,记录代码中各个 module 之间的关系。 每当有文件更新的时候, 递归过程会重来,graph 发生改变

    • 如果简单粗暴地重建 graph 再编译,会有很大的性能开销。 Webpack 利用缓存实现增量编译,从而提升构建性能

    • 在编译的时候会将 graph 以二进制或者 json 文件存储在硬盘上。每当代码变化、模块之间依赖关系改变导致 graph 改变时, Webpack 会读取记录做增量编译

    • 使用 cache-loader 可以将编译结果写入硬盘缓存,Webpack 再次构建时如果文件没有发生变化则会直接拉取缓存

    • 还有一部分 loader 自带缓存配置,比如 babel-loader,可以配置参数 cacheDirectory 使用缓存,将每次的编译结果写进磁盘(默认在 node_modules/.cache/babel-loader 目录)

    • 优化长期缓存(默认值自增 id)

    • 针对 moduleId 和 chunkId 的计算方式进行了优化

    • moduleId 根据上下文模块路径,chunkId 根据 chunk 内容计算,最后为 moduleId 和 chunkId 生成 3 - 4 位的数字 id,实现长期缓存,生产环境下默认开启

    • NodeJS 的 polyfill 脚本被移除

    • 最开始,Webpack 目标是允许在浏览器中运行 Node 模块。但是现在在 Webpack 看来,大多模块就是专门为前端开发的

    • 更好的 TreeShaking

    • v5 中会分析模块 export 与 import 之间的依赖关系,最终的代码生成非常简洁

    • Module Federation

      解决不同应用/项目中的模块热拔插(跨应用模块共享)

      达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了

    • NPM 方式共享模块(Lib)

      需要将依赖作为 Lib 安装到项目,进行 Webpack 打包构建再上线

      需要共享一个模块时,最常见的办法就是将其抽成通用依赖并分别安装在各自项目中

    • UMD 方式共享模块(ESI)通用模块定义规范

      将模块用 Webpack UMD 模式打包,并输出到其他项目中

      缺点:包体积无法达到本地编译时的优化效果,且库之间容易冲突

    • 微前端方式共享模块 micro-frontends (MFE)

      • 微前端也是最近比较火的模块共享管理方式,微前端就是要解决多项目并存问题,多项目并存的最大问题就是模块共享,不能有冲突
      • 资源加载方式上的缺点
      • 子应用独立打包,模块更解耦,但无法抽取公共依赖等
      • 整体应用一起打包,很好解决上面的问题,但打包速度实在是太慢了,不具备水平扩展能力
    • 模块联邦方式(Federated Module)

      直接将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力

      让应用具备模块化输出能力,其实开辟了一种新的应用形态,即 “中心应用”,这个中心应用用于在线动态分发 Runtime 子模块,并不直接提供给用户使用

       const HtmlWebpackPlugin = require("html-webpack-plugin"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); // 
       app1 module.exports = { 
         plugins: [    
           new ModuleFederationPlugin({name: "app_one_remote",  
       // // 当前应用名称,需要全局唯一       
        remotes: {  
         // 可以将其他项目的 name 映射到当前项目中       
         app_two: "app_two_remote",       
         app_three: "app_three_remote"       
        },       
         exposes: {  
           // 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用
          AppContainer: "./src/App"       
          },       
        shared: ["react", "react-dom", "react-router-dom"]  // 是非常重要的参数,制定了这个参数,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM    
        }),    
        new HtmlWebpackPlugin({       
          template: "./public/index.html",       
          chunks: ["main"]    }) ] }; // 
          app2 export default {    
            plugins: [    new ModuleFederationPlugin({       
              name: "app_two",  // 当前应用名称,需要全局唯一       
              library: { 
                type: "var", 
                name: "app_two" },       
                filename: "remoteEntry.js",       
                exposes: {       
                  Search: "./src/Search"       
                },       
                shared: ["react", "react-dom"]    
                }
            ) 
        ]};