webpack常见面试题

265 阅读4分钟

背景

收集整理了一波webpack常见题型,文章内容参考了多个大佬的博客,链接已放。同时,加上自己的理解,简单总结一波。如有错误,欢迎指教。

webpack整体原理说明

mp.weixin.qq.com/s?__biz=Mzg…

Tree Shaking

zhuanlan.zhihu.com/p/127804516 juejin.cn/post/695538…

借助静态模块分析,Tree-Shaking 实现的大体思路:借助 ES6 模块语法的静态结构,通过编译阶段的静态分析,找到没有引入的模块并打上标记,然后在压缩阶段利用像 uglify-js 这样的压缩工具删除这些没有用到的代码。

副作用

有副作用,就是不能被shaking删除,没有副作用,就是可以被删除。 配置sideEffects:false,就代表所有都没有副作用

一个副作用是:有一些代码,是在 import 时执行了一些行为,这些行为不一定和任何导出相关。例如 polyfill ,Polyfills 通常是在项目中全局引用,而不是在 index.js 中使用导入的方式引用。

Tree Shaking 并不能自动判断哪些脚本是副作用,因此手动指定它们非常重要

要将某些文件标记为副作用,我们需要将它们添加到package.json文件中

{
    ...,
    "sideEffects": [
        "./src/polyfill.js"
    ],
    ...,
}

或者在函数的左侧添加注释

/*#__PURE__*/ double(55);

按需加载

import()时可以通过注释语法import(/chunkName/'qqapi').then()来定义异步加载模块打包出来的chunkName,否则会默认以id作为chunkName

按需加载主要分为两种:

1.组件库按需加载:

组件库以组件为基本单位产出 js、css、less 文件,借助插件或者部分引入的写法,使得项目代码或 babel 编译后的代码中只包含使用到的组件的 js、css、less 等 image

2.webpack 懒加载:

webpack 将源码中的 import、require 引入的文件编译之后再根据动态加载语法配置(通常以页面路由为基本单位)将较大的代码拆分并构建出较小的 chunk 包,应用在运行时执行到相应业务逻辑时才去加载执行对应 chunk 代码。 webpack 懒加载主要发生在下图的 JS 拆分出不同的 Chunk 这一过程 image

可见,两者的差别主要在于:

两者执行时机不同,组件库按需加载是在源码编写阶段或者 babel 编译 js 阶段,而 webpack 懒加载则是在构建生成打包产物时,组件库按需加载在前,webpack 懒加载在后; 两者原理不同,组件库按需加载是在源码阶段就去掉了无关代码,而 webpack 懒加载则是将经过 tree-shaking 优化过后的大文件包进行拆分在适当的运行时进行按需加载。

作者:dino小恐龙 链接:juejin.cn/post/696850… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相同引入模块合并打包

webpack.docschina.org/guides/code… 配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:

配置entry,dependOn属性

 const path = require('path');

 module.exports = {
   mode: 'development',
   entry: {
    index: './src/index.js',
    another: './src/another-module.js',
    index: {
      import: './src/index.js',
      dependOn: 'shared',
    },
    another: {
      import: './src/another-module.js',
      dependOn: 'shared',
    },
    shared: 'lodash',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

单独打包页面 打包多个页面

survivejs.com/webpack/out… To generate multiple pages with webpack, we can leverage mini-html-webpack-plugin. html-webpack-plugin would work well for the purpose as well and using it would give you access to the plugins written for it. For the demonstration, using the former is enough.

A page should receive title, url, and chunks for deciding which scripts to include to the page. The idea can be modeled as a configuration part as below:

// webpack.parts.js
const {
  MiniHtmlWebpackPlugin,
} = require("mini-html-webpack-plugin");

exports.page = ({ title, url = "", chunks } = {}) => ({
  plugins: [
    new MiniHtmlWebpackPlugin({
      publicPath: "/",
      chunks,
      filename: `${url && url + "/"}index.html`,
      context: { title },
    }),
  ],
});
// webpack.multi.js

const { merge } = require("webpack-merge");
const parts = require("./webpack.parts");

module.exports = merge(
  { mode: "production", entry: { app: "./src/multi.js" } },
  parts.page({ title: "Demo" }),
  parts.page({ title: "Another", url: "another" })
);

如何实现loader

webpack.docschina.org/contribute/… juejin.cn/post/684490…

// syncloader.ja
module.exports = function (source) {
    console.log('source>>>>', source)
    // source是源码,可以直接修改源码加注释
    source += '// 升值加薪'
    return source
}

const path = require('path')
module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    resolveLoader: {
        // loader路径查找顺序从左往右
        // 这里必须加,不然会找不到
        modules: ['node_modules', './']
    },
    module: {
        rules: [
        // 这里也要配置具体使用的loader
            {
                test: /\.js$/,
                use: 'syncLoader'
            }
        ]
    }
}


实现plugin

首先应该明确的是,plugin应该是一个类。 一般都是new Plugin()这种形式使用 webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。 webpack.docschina.org/concepts/pl…

比如说,生成1个readme

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('webpack 构建正在启动!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

class DemoWebpackPlugin {
  constructor () {
      console.log('plugin init')
  }
  // compiler是webpack实例
  apply (compiler) {
      // 一个新的编译(compilation)创建之后(同步)
      // compilation代表每一次执行打包,独立的编译
      compiler.hooks.compile.tap('DemoWebpackPlugin', compilation => {
          console.log(compilation)
      })
      // 生成资源到 output 目录之前(异步)
      compiler.hooks.emit.tapAsync('DemoWebpackPlugin', (compilation, fn) => {
          console.log(compilation)
          compilation.assets['index.md'] = {
              // 文件内容
              source: function () {
                  return 'this is a demo for plugin'
              },
              // 文件尺寸
              size: function () {
                  return 25
              }
          }
          fn()
      })
  }
}

module.exports = DemoWebpackPlugin


感谢阅读,欢迎指教