换坑备货之Webpack篇

2020年许一个愿望:进驻大厂!愿望有了,离大厂还 远吗 差十万八千里!那看完这篇文章呢?(并没有什么卵用)那看完换坑备货系列呢?谁说得定呢!看了才知道(认真脸)

  • 换坑备货之Webpack篇,
  • 换坑备货之Node.js篇,
  • 换坑备货之Vue篇,
  • 换坑备货之http篇,
  • 换坑备货之性能优化篇,
  • 换坑备货之正则与算法篇,
  • 换坑备货之工程化篇

文章是笔者花了大量时间参考了很大前辈的文章整理出来的,如有什么不对的地方请不吝赐教!关于文章所提到所有配置由于篇幅过长所以没有贴出来,这里送上传送门 Webpack 4 配置Vue多页面打包最佳实践

一、Webpack 是什么

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。 核心概念:

  • 入口(entry)

    其指示 Webpack 应该用哪个模块,来作为构建其内部依赖图的开始,进入入口起点后,Webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

  • 输出(output)

    output 属性告诉 Webpack 在哪里输出它所创建的bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。

  • module

    模块,在 Webpack 里一切皆模块,在Webpack中,CSS、HTML、js、静态资源文件等都可以视作模块,Webpack 会从配置的 Entry 开始递归找出所有依赖的模块,一个模块对应着一个文件。

  • Chunk

    代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割

  • loader

    loader 让 Webpack 能够去处理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。 loader 可以将所有类型的文件转换为 Webpack 能够处理的有效模块,然后你就可以利用 Webpack 的打包能力,对它们进行处理。 本质上,Webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块

  • 插件(Plugins)

    plugin 用来扩展Webpack的功能,其通过构建流程的特定时机注入钩子实现的,插件接口功能极其强大,给Webpack带来很大的灵活性。

二、Webpack 有什么特点

模块化,压缩,打包 具体作用:

  1. 搭建开发环境开启服务器,监视文件改动,热更新
  2. 通过建立依赖图把模块打包成一个或者多个chuck。
  3. 通过loader 把将sass/less、图片、vue等文件转成Webpack可以识别的格式的文件
  4. 能够通过插件对资源进行模块分离,压缩,整合等

三、webapck 打包流程

详细流程:

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
  3. 确定入口:根据配置中的 entry 找出所有的入口文件。
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

简单流程:

  1. 入口文件开始分析
  • 哪些依赖文件
  • 转换代码
  1. 递归分析依赖代码
  • 哪些依赖文件
  • 转换代码
  1. 生成浏览器可以识别执行的bundle文件

四、Webapck 打包原理

  1. 通过fs.readFileSync读取入口文件,然后通过@babel/parser获取ast抽象语法树,借助@babel/core和 @babel/preset-env,把ast语法树转换成合适的代码最后输出一个文件对象,下面举个栗子:

打包入口文件为index.js:

import { say } from "./hello.js";
console.log(say('something'))
复制代码

依赖文件hello.js

export function say(str) {return str}
复制代码

生成文件对象

{
   filename:'index.js,' // 文件路径
   dependencies:'./src/hello.js',//依赖文件
   code:'\"use strict\";\n\nvar _hello = require(\"./hello.js\");\n\n/....' // 代码
  }
复制代码

然后递归dependencies,最后生成:

[{
   filename:'index.js,' // 文件路径
   dependencies:'./src/hello.js',//依赖文件
   code:'\"use strict\";\n\nvar _hello = require(\"./hello.js\");\n\n/....' // 代码
  },{
   filename:'hello.js,' // 文件路径
   dependencies:{},
   code:"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n}); ...
  }
]
复制代码
  1. 由于生成的代码还包含了浏览器无法识别require函数,所以实现了一 个webpack_require替换require来实现模块化,通过自执行函数传入index文件对象 执行 eval(code),然后递归dependencies执行依赖文件hello文件对象的code。

五、常见优化手段

  • MiniCssExtractPlugin插件:对CSS进行分离和压缩
  • happypack插件:HappyPack开启多个线程打包资源文件
  • DllPlugin、DllReferencePlugin插件:DllPlugin通过配置Webpack.dll.conf.js把第三方库:vue、vuex、element-ui等打包到一个bundle的dll文件里面,同时会生成一个名为 manifest.json映射文件,最后使用 DllReferencePlugin检测manifest.json映射,过滤掉已经存在映射的包,避免再次打包进bundle.js。
  • ParallelUglifyPlugin插件:开启多个子进程压缩输出的 JS 代码(Webpack4.0 默认使用了 TerserWebpackPlugin,默认就开启了多进程和缓存)
  • optimization.splitChunks:抽离公共文件
  • 其他:exclude/include配置、externals配置、使用cache-loader等

六、Webpack 常见面试题

  1. 热更新原理
    监听文件变动,通过websocket协议自动刷新网页(详细内容还没有深入研究)
  2. loader与plugin的区别
    loader: loader是一个转换器是在 import 或"加载"模块时预处理文件,将A语言转成B语言,如 TypeScript转换为 JavaScript,less转成CSS,单纯的文件转换成浏览器可以识别的文件。
    plugin:插件是一个扩展器,目的在于解决 loader 无法实现的其他事。
  3. 常用的loader和常见plugin有哪些
    loader:
  • babel-loader:把 ES6 转换成 ES5
  • less-loader:将less代码转换成CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • vue-loader:加载 Vue.js 单文件组件
  • cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里
  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值时返回其 publicPath,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
    plugin:
  • CopyWebpackPlugin:将单个文件或整个目录复制到构建目录
  • HtmlWebapckPlugin:简单创建 HTML 文件,用于服务器访问
  • ParallelUglifyPlugin: 多进程执行代码压缩,提升构建速度
  • MiniCssExtractPlugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-Webpack-plugin)
  1. 用过哪些可以提高效率的webapck插件 此答案请参考目录五、常见优化手段
  2. 实现一个简单的loader

实现一个替换源码中字符的loader

//index.js
console.log("hello");

//replaceLoader.js
module.exports = function(source) {
  // source是源码
  return source.replace('hello','hello loader') 
};

复制代码

在配置文件中使用loader

//需要node模块path来处理路径 
const path = require('path')
module: {
rules: [ {
         test: /\.js$/,
         use: path.resolve(__dirname,"./loader/replaceLoader.js")
}]
}
复制代码
  1. Webpack插件原理,如何写一个插件

原理:在 Webpack 运行的生命周期中会广播出许多事件(run、compile、emit等),Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

实现一个copy功能的Plugin

class CopyrightWebpackPlugin {
  //compiler:webpack实例
  apply(compiler) {
    //emit 生成资源文件到输出目录之前
    compiler.hooks.emit.tapAsync(
      "CopyrightWebpackPlugin",
      (compilation, cb) => {
        // assets目录输出copyright.txt
        compilation.assets["copyright.txt"] = {
          // 文件内容
          source: function() {
            return "hello copy";
          },
          // 文件大小
          size: function() {
            return 20;
          }
        };
        // 完成之后 走回调,告诉compilation事情结束
        cb();
      }
    );
    // 同步的写法;
    compiler.hooks.compile.tap("CopyrightWebpackPlugin", compilation => {
      console.log("开始了");
    });
  }
}
module.exports = CopyrightWebpackPlugin;

复制代码

使用CopyrightWebpackPlugin插件

const CopyrightWebpackPlugin = require("./plugins/copyright-webpack-plugin");
plugins: [
    new CopyrightWebpackPlugin()
  ]
复制代码
  1. Webpack的require是如何如何查找依赖的
  • 解析相对路径:查找相对当前模块的路径下是否有对应文件或文件夹是文件则直接加载,是文件夹则继续查找文件夹下的 package.json 文件 有 package.json 文件则按照文件中 main 字段的文件名来查找文件 无 package.json 或者无 main 字段则查找 index.js 文件
  • 解析模块名:查找当前文件目录下,父级目录及以上目录下的 node_modules 文件夹,看是否有对应名称的模块
  • 解析绝对路径:直接查找对应路径的文件

文章参考:

Web全栈架构师第12期

Webpack深入浅出

Webpack打包原理 ? 看完这篇你就懂了 !

「吐血整理」再来一打Webpack面试题:fire:(持续更新)

带你深度解锁Webpack系列(优化篇)

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章