webpack知识体系详解

166 阅读11分钟

webpack基础概念

webpack 是一个模块打包器,将根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源。

当 webpack 处理程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

entry

打包的入口文件

单入口:

entry:'./src/index.js'

多入口:

entry:{
    home:'./src/home.js',
    other:'./src/other/js'
}

mode

  • production:生产模式,打包比较慢,会开启 tree-shaking 和 压缩代码
  • development:开发模式,打包更加快速,省了代码优化步骤

output

输出文件

    output:{
        filename:'[name].[hash:8].js', // 输出文件名
        path:path.join(__dirname,'dist') // 输出文件地址
    }

loaders

webpack开箱只支持js和json两种文件类型,通过loaders去支持其他文件类型并且把他们转化成有效的模块,并且可以添加到依赖图中

本身是一个函数,接收源文件作为参数,返回转化的结果

常见loader

  • babel-loader 转换ES6 ES7等JS新特性语法

npm install babel-loader @babel/core @babel/preset-env -D

babel-loader 使用 Babel 加载 ES2015+ 代码并将其转换为 ES5

@babel/core Babel 编译的核心包

@babel/preset-env Babel 编译的预设,可以理解为 Babel 插件的超集

  • css-loader 解析css
  • style-loader 让样式正确显示在页面上
  • less-loader 将less转成css
  • postcss-loader 通常用来进行浏览器css兼容性处理
  • ts-loader 将ts转成js
  • file-loader 进行图片字体的打包
  • raw-loader 将文件以字符串的形式导入

style-loader的核心逻辑相当于:

const content = `${样式内容}`
const style = document.createElement('style');
style.innerHTML = content;
document.head.appendChild(style);

loader加载顺序是从右向左

loader的本质:loader就是一个导出为函数的js模块,函数接收源文件内容或者是被其他loader处理过的内容,然后进行自己的一些处理操作,在将结果暴露出去

plugin

html-webpack-plugin

该插件将为你生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle

当使用 webpack打包时,创建一个 html 文件,并把 webpack 打包后的静态文件自动插入到这个 html 文件当中

const HtmlWebpackPlugin = require('html-webpack-plugin')

... 
其他配置
...

plugins:[
    // 将JS css文件自动引入到html中
    new HtmlWebpackPlugin({
        template:'./src/index.html'
    })
]

如果是多入口,则采用下面的配置

plugins:[
    new HtmlWebpackPlugin({
      template:path.resolve(__dirname,'../public/index.html'),
      filename:'index.html',
      chunks:['main'] // 与入口文件对应的模块名
    }),
    new HtmlWebpackPlugin({
      template:path.resolve(__dirname,'../public/header.html'),
      filename:'header.html',
      chunks:['header'] // 与入口文件对应的模块名
    }),
  ]

clean-webpack-plugin

帮助我们每次打包前清理dist

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

    plugins:[
        new CleanWebpackPlugin()
    ],

mini-css-extract-plugin

css默认是打包在html中的style标签内,如果向拆分出来就需要mini-css-extract-plugin

注意:这里还需要修改loader

    {
        test:/\.less$/,
        use:[
            // 'style-loader',
            MiniCssExtractPlugin.loader,
            'cache-loader', // 获取前面 loader 转换的结果  cache-loader : 缓存一些性能开销比较大的 loader 的处理结果
            'css-loader',
            'postcss-loader',
            'less-loader'
        ]
    },
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    plugins:[
        new MiniCssExtractPlugin({
            filename:'[name].[hash:8].css',
            chunkFilename: "[id].css"
        })
    ],

手写一个插件

插件基本结构就是一个有apply方法的构造函数,里面执行一些插件的钩子,apply方法是用来获取compiler的

// jszip是一个用于创建、读取和编辑.zip文件的JavaScript库
const JSZip = require('jszip');
const path = require('path');
const RawSource = require('webpack-sources').RawSource;
const zip = new JSZip();

module.exports = class ZipPlugin {
    constructor(options) {
        this.options = options;
    }

    apply(compiler) {
        // emit是compiler的一个钩子 emit: 输出 asset到output 目录之前执行。这个钩子不会被复制到子编译器
        // 异步钩子用tapAsync执行,同步用tap执行
        compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => {
            // 添加一个文件夹
            const folder = zip.folder(this.options.filename);
            // 遍历打包的资源
            for (let filename in compilation.assets) {
                // 获取每个文件的source
                const source = compilation.assets[filename].source();
                // 将资源文件添加到文件夹里
                folder.file(filename, source);
            }
            // 生成zip文件
            zip.generateAsync({
                type: 'nodebuffer'
            }).then((content) => {
                const outputPath = path.join(
                    compilation.options.output.path, 
                    this.options.filename + '.zip'
                );
                // 确定文件输出路径
                const outputRelativePath = path.relative(
                    compilation.options.output.path,
                    outputPath
                );
                // 通过 RawSource 向 compilation.assets写入zip包
                compilation.assets[outputRelativePath] = new RawSource(content);
                // 继续执行后面的操作
                callback();
            });
        });
    }
}

devServer

npm intall webpack-dev-server -D

    devServer:{
        contentBase:path.resolve(__dirname,'public'),
        compress:true,// 是否启动gzip压缩
        port:8080, // 端口号
        open:true // 是否自动打开浏览器
    }

为什么要配置contentBase?

因为 webpack 在进行打包的时候,对静态文件的处理,例如图片,都是直接 copy 到 dist 目录下面。但是对于本地开发来说,这个过程太费时,也没有必要,所以在设置 contentBase 之后,就直接到对应的静态目录下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。

多页面打包思路

  1. 每个页面对应一个entry,同时对应一个html-webpack-plugin

缺点:每次新增或删除页面都需要改webpack配置

  1. 动态获取entry和设置html-webpack-plugin

利用glob.sync(同步读取文件)

const glob = require('glob');

const setMPA = () => {
    const entry = {};
    const htmlWebpackPlugins = [];
    const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));

    Object.keys(entryFiles)
        .map((index) => {
            const entryFile = entryFiles[index];
            // '/Users/cpselvis/my-project/src/index/index.js'

            const match = entryFile.match(/src\/(.*)\/index\.js/);
            const pageName = match && match[1];

            entry[pageName] = entryFile;
            htmlWebpackPlugins.push(
                new HtmlWebpackPlugin({
                    template: path.join(__dirname, `src/${pageName}/index.html`),
                    filename: `${pageName}.html`,
                    chunks: [pageName],
                    inject: true,
                    minify: {
                        html5: true,
                        collapseWhitespace: true,
                        preserveLineBreaks: false,
                        minifyCSS: true,
                        minifyJS: true,
                        removeComments: false
                    }
                })
            );
        });

    return {
        entry,
        htmlWebpackPlugins
    }
}

const { entry, htmlWebpackPlugins } = setMPA();

 module.exports = {
     entry:entry,
     plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new CleanWebpackPlugin(),
        new FriendlyErrorsWebpackPlugin()
    ].concat(htmlWebpackPlugins),
}

sourceMap

sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中debug问题会带来非常糟糕的体验,sourceMap可以帮助我们快速定位到源代码的位置,提高我们的开发效率

开发环境开启,线上环境关闭

sourceMap关键字

关键字描述
inline代码内通过 dataUrl 形式引入 SourceMap 不单独生成.map文件
hidden生成 SourceMap 文件,但不使用
evaleval(...) 包裹模块代码
nosources不生成 SourceMap
cheap只需要定位到行信息,不需要列信息
module展示源代码中的错误位置 包含loader的sourceMap
  • inline:内联,一个chunk生成一个总的source-map
  • eval:内联,每一个文件生成一个source-map
  • cheap:外部,报错位置只能精确到行。
  • cheap-module:显示第三方库的source-map
devtoolbuildrebuild显示代码SourceMap 文件描述
(none)很快很快无法定位错误
eval很快(cache)编译后定位到文件
source-map很慢很慢源代码定位到行列
eval-source-map很慢一般(cache)编译后有(dataUrl)定位到行列
eval-cheap-source-map一般快(cache)编译后有(dataUrl)定位到行
eval-cheap-module-source-map快(cache)源代码有(dataUrl)定位到行
inline-source-map很慢很慢源代码有(dataUrl)定位到行列
hidden-source-map很慢很慢源代码无法定位错误
nosource-source-map很慢很慢源代码定位到文件

参考:当面试官问Webpack的时候他想知道什么

css压缩

npm install -D optimize-css-assets-webpack-plugin


var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');


const config = {
  // ...
  optimization: {
    minimize: true, // 开启最小化
    minimizer: [
      // ...
      new OptimizeCssAssetsPlugin({})
    ]
  },
  // ...
}

SplitChunksPlugin 分包配置

    optimization:{
      splitChunks:{
        // chunks:'all', // 有效值为 `all`(所有引入的库都进行分离)`async`(异步引入的库分离) 和 `initial`(同步引入库分离)
        // minSize:20000, // 生成chunk的最小体积
        // minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块
        // minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。 最小引用数
        // maxAsyncRequests: 30, // 最大的按需(异步)加载次数
        // maxInitialRequests: 30, // 打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件)
        cacheGroups: { // 配置提取模块的方案
          default: false,
          styles: {
              name: 'styles',
              test: /\.(s?css|less|sass)$/,
              chunks: 'all',
              enforce: true,
              priority: 10,
            },
            common: {
              name: 'chunk-common',
              chunks: 'all',
              minChunks: 2, // 做少引用次数
              maxInitialRequests: 5,
              minSize: 0,
              priority: 1,
              enforce: true,
              reuseExistingChunk: true,
            },
            vendors: {
              name: 'chunk-vendors',
              test: /[\\/]node_modules[\\/]/,
              chunks: 'all',
              priority: 2,
              enforce: true,
              reuseExistingChunk: true,
            },
           // ... 根据不同项目再细化拆分内容
        }
      }
    }

tree shaking 摇树优化

Tree-shaking 作用是剔除没有使用的代码,以降低包的体积

webpack 默认支持,需要在 .bablerc 里面设置 model:false即可,生产环境下默认开启

要求:必须是ES6的语法方式引入 commonjs不支持

tree shaking原理

Tree-shaking的原理主要是依赖于ES6的模块特性

ES6模块的特点:

  • 只能作为模块顶层的语句出现 (import)
  • import的模块名只能是字符串常量
  • import binding是immutable的 (import之后不能修改)

ES6模块依赖关系是确定的,和运行时的状态无关。可以进行可靠的静态分析,这就是tree-shaking的基础

本质是编译是对代码进行静态分析,在uglify阶段删除无效代码

Scope Hoisting(作用域提升)

不开启Scope Hoisting的编译结果会存在大量的闭包代码,导致体积增加

运行代码时创建的函数作用域变多,内存开销变大

模块转换分析:

  • 被wabpack转换后的模块会带上一层包裹
  • import会被转换成__webpack_require

Scope Hoisting 即作用域提升,原理是将多个模块放在同一个作用域下,并重命名防止命名冲突,通过这种方式可以减少函数声明和内存开销

  • webpack 默认支持,在生产环境下默认开启
  • 只支持 es6 代码

speed-measure-webpack-plugin(速度分析)

npm i -D speed-measure-webpack-plugin

...
// 费时分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
...

const config = {...}

module.exports = (env, argv) => {
  // 这里可以通过不同的模式修改 config 配置


  return smp.wrap(config);
}

webpack-bundle-analyzer(体积分析)

npm i -D webpack-bundle-analyzer

// 引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin


const config = {
  // ...
  plugins:[ 
    // ...
    // 配置插件 
    new BundleAnalyzerPlugin({
      // analyzerMode: 'disabled',  // 不启动展示打包报告的http服务器
      // generateStatsFile: true, // 是否生成stats.json文件
    })
  ],
};

修改启动项

 "scripts": {
    "analyzer": "cross-env NODE_ENV=prod webpack --progress --mode production"
  },

多进程/多实例构建

  • thread-loader(官方推荐)
  • paraller-webpack
  • HappyPack

利用缓存提升二次构建速度

  • babel-loader 开启缓存
use:[{
    loader:'babel-loader',
    options:{
        cacheDirectory:true
    }
}]
  • terser-webpack-plugin 开启缓存
  • cache-loader 或者 hard-source-webpack-plugin

hard-source-webpack-plugin 利用缓存时,配置文件不能发生更改,如果配置文件的配置项每次都更改,则该功能不生效

webpack热更新原理

一般的刷新分为两种:

  1. 页面刷新,不保留页面状态,就是简单粗暴的window.location.reload()
  2. 基于WDS的模块热替换,只需要局部刷新页面上发生变化的模块,同时可以保留页面的状态

当我们开启热更新时,获取更新文件,浏览器会请求两个文件,一个是以hot-update.json 结尾的文件,一个是以hot-update.js结尾的文件

具体内容如下:

  • JSON文件
{
    "h": "0c256052432b51ed32c8",
    "c": {
        "201": true
    }
}

h代表本次新生成的Hash值,本次输出的hash值会被做为下次热更新的标识,c表示当前需要热更新的文件对应的是哪个模块

  • JS文件

是本次修改的代码,重新编译打包后的,webpackHotUpdate 方法就是用来更新模块的,201 对应的是哪个模块(我们称它为模块标识),其他的就是要更新的模块的内容了

webpackHotUpdate(201, {
    "./src/views/moveTransfer/list/index.vue?vue&type=script&lang=js&": function (
      module,
      exports,
      __webpack_require__
    ) {
      "use strict";
  
      var _Object$defineProperty = __webpack_require__(
        /*! @babel/runtime-corejs3/core-js-stable/object/define-property */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js"
      );
  
      _Object$defineProperty(exports, "__esModule", {
        value: true,
      });
  
      exports.default = void 0;
  
      var _default = {
        data: function data() {
          return {};
        },
        computed: {},
        methods: {
          clickMe: function clickMe() {
          },
        },
      };
      exports.default = _default;
    },
  });
  
  

热更新使用了websocket进行浏览器和webpack服务器的通信

热更新过程

几个重要的概念

  • Webpack-complierwebpack 的编译器,将 JavaScript 编译成 bundle(就是最终的输出文件)

  • HMR Server:将热更新的文件输出给 HMR Runtime

  • Bunble Server:提供文件在浏览器的访问,也就是我们平时能够正常通过 localhost 访问我们本地网站的原因

  • HMR Runtime:开启了热更新的话,在打包阶段会被注入到浏览器中的 bundle.js,这样 bundle.js 就可以跟服务器建立连接,通常是使用 websocket ,当收到服务器的更新指令的时候,就去更新文件的变化

  • bundle.js:构建输出的文件

启动阶段

文件经过 Webpack-complier 编译好后传输给 Bundle ServerBundle Server 可以让浏览器访问到我们打包出来的文件

热更新阶段

文件经过 Webpack-complier 编译好后传输给 HMR ServerHMR Server 知道哪个资源(模块)发生了改变,并通知 HMR Runtime 有哪些变化(也就是上面我们看到的两个请求),HMR Runtime 就会更新我们的代码,这样我们浏览器就会更新并且不需要刷新

本地修改了文件,浏览器是怎么知道要更新的呢

本地实际上启动了一个 HMR Server 服务,而且在启动 Bundle Server 的时候已经往我们的 bundle.js 中注入了 HMR Runtime(主要用来启动 Websocket,接受 HMR Server 发来的变更)

所以我们聚焦以下几点:

  • Webpack 如何启动了 HMR Server
  • HMR Server 如何跟 HMR Runtime 进行通信的
  • HMR Runtime 接受到变更之后,如何生效的

启动HMR Server

该工作主要是在webpack-dev-server中完成,查看源码,在webpack-dev-server/lib/Server.js中 setupApp方法,该方法启动了一个express服务器,实际上对应的是 Bundle Server

setupApp() {
  // Init express server
  // eslint-disable-next-line new-cap
  // 初始化 express 服务
  // 使用 express 框架启动本地 server,让浏览器可以请求本地的静态资源。
  this.app = new express();
}

listeningApp监听到启动成功,就会通过createSocketServer创建一个websocket服务

  listen(port, hostname, fn) {
    this.hostname = hostname;

    return this.listeningApp.listen(port, hostname, (err) => {
     // 启动 express 服务之后,启动 websocket 服务
      this.createSocketServer();

      if (this.options.bonjour) {
        runBonjour(this.options);
      }

      this.showStatus();

      if (fn) {
        fn.call(this.listeningApp, err);
      }

      if (typeof this.options.onListening === 'function') {
        this.options.onListening(this);
      }
    });
  }
  
  createSocketServer() {
  this.socketServer = new this.SocketServerImplementation(this);

  this.socketServer.onConnection((connection, headers) => {
	
  });
}

HMR Server和HMR Runtime通信

首先我们要确认的是,通信的时机,什么时候去通知客户端我的文件更新。通过 webpack 创建的 compiler 实例(监听本地文件的变化、文件改变自动编译、编译输出),可以往 compiler.hooks.done 钩子(代表 webpack 编译完之后触发)注册事件, 当监听到一次 webpack 编译结束,就会调用 sendStats 方法

// lib/Server.js
// 绑定监听事件
setupHooks() {
  // ...
  const addHooks = (compiler) => {
    // 监听 webpack 的 done 钩子,tapable 提供的监听方法
    // done 标识编译结束
    const { compile, invalid, done } = compiler.hooks;
    compile.tap('webpack-dev-server', invalidPlugin);
    invalid.tap('webpack-dev-server', invalidPlugin);
    done.tap('webpack-dev-server', (stats) => {
      // 当监听到一次webpack编译结束,就会调用 sendStats 方法
      this.sendStats(this.sockets, this.getStats(stats));
      this.stats = stats;
    });
  };
}

当监听到一次 webpack 编译结束,就会调用 sendStats 方法,里面会向客户端发送 hash 和 ok 事件

  _sendStats(sockets, stats, force) {
    const shouldEmit =
      !force &&
      stats &&
      (!stats.errors || stats.errors.length === 0) &&
      stats.assets &&
      stats.assets.every((asset) => !asset.emitted);

    if (shouldEmit) {
      return this.sockWrite(sockets, 'still-ok');
    }

    this.sockWrite(sockets, 'hash', stats.hash);

    if (stats.errors.length > 0) {
      this.sockWrite(sockets, 'errors', stats.errors);
    } else if (stats.warnings.length > 0) {
      this.sockWrite(sockets, 'warnings', stats.warnings);
    } else {
      this.sockWrite(sockets, 'ok');
    }
  }

在client/index.js中,会去更新hash,并且在ok的时候去进行更新检查reloadApp

// client-src/default/index.js 
const onSocketMessage = {
  // 更新 current Hash
  hash(hash) {
    status.currentHash = hash;
  },
  'progress-update': function progressUpdate(data) {
    if (options.useProgress) {
      log.info(`${data.percent}% - ${data.msg}.`);
    }

    sendMessage('Progress', data);
  },
  ok() {
    sendMessage('Ok');

    if (options.useWarningOverlay || options.useErrorOverlay) {
      overlay.clear();
    }

    if (options.initial) {
      return (options.initial = false);
    }
    // 进行更新检查等操作
    reloadApp(options, status);
  }

};

接下来我们看看 client-src/default/utils/reloadApp.js 中的 reloadApp。这里又利用 node.jsEventEmitter,发出webpackHotUpdate 消息。这里又将更新的事情给回了 webpack(为了更好的维护代码,以及职责划分的更明确

function reloadApp(
  { hotReload, hot, liveReload },
  { isUnloading, currentHash }
) {
  // ...
  if (hot) {
    log.info('App hot update...');
    //  hotEmitter 其实就是 EventEmitter 的实例
    const hotEmitter = require('webpack/hot/emitter');
    // 又利用 node.js 的 EventEmitter,发出 webpackHotUpdate 消息。
    // websocket 仅仅用于客户端(浏览器)和服务端进行通信。而真正做事情的活还是交回给了 webpack。
    hotEmitter.emit('webpackHotUpdate', currentHash);
    if (typeof self !== 'undefined' && self.window) {
      // broadcast update to window
      self.postMessage(`webpackHotUpdate${currentHash}`, '*');
    }
  }
  // ...
}

module.exports = reloadApp;

在 webpack 的 hot/dev-server.js 中,监听 webpackHotUpdate 事件,并执行 check 方法。并在 check 方法中调用 module.hot.check 方法进行热更新

// hot/dev-server.js
// 监听webpackHotUpdate事件
hotEmitter.on("webpackHotUpdate", function (currentHash) {
  lastHash = currentHash;
  if (!upToDate() && module.hot.status() === "idle") {
    log("info", "[HMR] Checking for updates on the server...");
    check();
  }

var check = function check() {
  //  moudle.hot.check 开始热更新
  // 之后的源码都是HotModuleReplacementPlugin塞入到bundle.js中的哦,我就不写文件路径了
  module.hot
    .check(true)
    .then(function (updatedModules) {
      // ...
    })
    .catch(function (err) {
      // ...
    });
};

至于 module.hot.check ,实际上通过 HotModuleReplacementPlugin 已经注入到我们 chunk 中了(也就是我们上面所说的 HMR Runtime),所以后面就是它是如何更新 bundle.js 的呢

看完这篇,再也不怕被问 Webpack 热更新

搞懂webpack热更新原理

轻松理解webpack热更新原理

webpack 4.x 对比3.x版本进行了哪些优化,为什么提高了构建速度?

  1. webpack4增加了mode配置项,可以对不同的环境开启不同的配置

  2. 不再支持CommonsChunkPlugin,改用optimization.splitChunks

  3. 不在支持UglifyJsPlugin,通过optimization.minimize = true进行设置

  4. 移除了loaders,必须使用rules去配置loader

速度的提升包括以下几方面:

  • V8 带来的 优化( for of 代替了 forEach,Map 和 Set 代替了 Object,includes 代替了 indexOf )。
  • 默认使用更快的 md4 hash 算法。
  • webpack AST 可以直接从loader传递给 AST,减少解析时间。
  • 使用字符串方法代替正则 表达式

webpack5 有哪些新特性

  1. 内置静态资源的构建能力

在webpack5之前,我们一般都会使用file-loader、url-loader、raw-loader来处理静态资源,而webpack提供了内置的静态资源构建能力,我们不需要安装额外的 loader,仅需要简单的配置就能实现静态资源的打包和分目录存放。

// webpack.config.js
module.exports = {
    ...,
    module: {
      rules: [
          {
            test: /.(png|jpg|svg|gif)$/,
            type: 'asset/resource',
            generator: {
                // [ext]前面自带"."
                filename: 'assets/[hash:8].[name][ext]',
            },
        },
      ],
    },
}

  1. 内置文件系统缓存

Webpack5 之前,我们会使用 cache-loader 缓存一些性能开销较大的 loader ,或者是使用 hard-source-webpack-plugin 为模块提供一些中间缓存。在 Webpack5 之后,默认就为我们集成了一种自带的缓存能力(对 module 和 chunks 进行缓存

// webpack.config.js
module.exports = {
    ...,
    cache: {
        type: 'filesystem',
        // 可选配置
        buildDependencies: {
            config: [__filename],  // 当构建依赖的config文件(通过 require 依赖)内容发生变化时,缓存失效
        },
        name: '',  // 配置以name为隔离,创建不同的缓存文件,如生成PC或mobile不同的配置缓存
        ...,
    },
}

  1. 内置worker的构建能力

以前使用webpack,如果在项目中使用了webworker,需要手动安装work-loader并配置,还需要针对worker配置特别的work.js的文件名。

在 Webpack5 中,我们不需要添加 loader 的处理方式,并且不需要针对 worker 配置特定的 .worker.js 之类的文件名,借助于 new URL, 便能实现 worker 的创建

// master.js
const worker = new Worker(new URL('./calc.js', import.meta.url), {
    name: "calc"
  /* webpackEntryOptions: { filename: "workers/[name].js" } */
});
worker.onmessage = e => {
  console.log(e.data.value);
};

  1. 不在为nodeJS模块提供自动引用Polyfills

在早期,webpack 的目的是为了让大多数的 Node.js 模块运行在浏览器中,但如今模块的格局已经发生了变化,现在许多模块主要是为前端而编写。Webpack <= 4 的版本中提供了许多 Node.js 核心模块的 polyfills,一旦某个模块引用了任何一个核心模块(如 cypto 模块),webpack 就会自动引用这些 polyfills。

尽管这会使得使用为 Node.js 编写模块变得容易,但它在构建时给 bundle 附加了庞大的 polyfills。在大部分情况下,这些 polyfills 并非必须

  1. 支持命名代码块ID

比如以前在vue路由懒加载中会写

import(/* webpackChunkName: "name" */ "module")

现在可以在生产环境中使用 chunkIds: "named"命名

  1. 对Tree-shaking进行了优化

Webpack5 能够跟踪对导出的嵌套属性的访问,所以支持嵌套模块的Tree-shaking分析,也能够处理commonJS的Tree-shaking

  1. 新增了长期缓存算法,生产环境下默认启用

  2. 新增了模块联邦功能,即允许多个webpack构建一起工作

webpack和gulp的区别

gulp的核心是task,通过配置一系列task,并且定义task要处理的事务(比如js压缩,css压缩,less编译),之后让gulp来依次执行这些task,让整个流程自动化。本质是一个工具链,所以gulp是一种前端自动化任务管理工具。

webpack:是静态文件打包工具,可以把项目的各种js文件、css文件等打包合并成一个或多个文件,主要用于模块化方案,预编译模块的方案

如果工程模块依赖非常简单,甚至没有用到模块化的概念。只需要进行简单的合并、压缩,就使用grunt/gulp即可。但是如果整个项目使用了模块化管理,而且相互以来非常强,我们就可以使用更加强大的webpack了

Vite和webpack的区别

webpack core 是一个纯打包工具(对标 Rollup),而 Vite 其实是一个更上层的工具链方案,对标的是 (webpack + 针对 web 的常用配置 + webpack-dev-server)

webpack优点:灵活,不局限于web打包,几乎所有的环节都设置成了可配置的

webpack缺点:配置项极度复杂,插件机制和内部逻辑晦涩难懂,针对常见的 web 也需要大量的配置

vite:实际上是通过预设了场景去降低复杂度,比如将常见的 web 构建需求都可以直接做成默认内置

vite的特点:

  1. index.html中入口文件的导入指定了type=module,这样main.js中就可以使用ES6 Module方式组织代码,浏览器会自动加载这些导入

  2. 支持直接导入css文件

  3. 安装对应的样式预处理器就可以直接使用,而不必再去配置loader之类的操作

  4. 默认支持TS

  5. vite按需编译当前页面需要的组件,而不需要打包整个APP的组件