loader和plugin

  • loader翻译为加载器
    • webpack将一切文件视为模块,但是webpack 只能解析原生的js文件,如果想要打包其他格式的文件的话,就需要用到loader
    • loader的作用就是让webpack拥有了可以加载和解析非js文件的功能
  • plugin翻译为插件
    • 可以扩展webpack的功能,让webpack 更加灵活
    • webpack的运行生命周期中有许多事件,plugin可以监听这些事件,在合适的时候通过webpack的api改变输出结果

Hot Module Replacement

可以只更新自己想要更新的模块,选择性更新

  1. plugins: [
  2. ...
  3. new webpack.HotModuleReplacementPlugin()
  4. ],
  1. import counter from "./counter.js";
  2. import number from "./number.js";
  3. number();
  4. counter();
  5. if (module.hot) {
  6. // 如果number这个文件发生了变化,那么就会执行后面的回调函数
  7. module.hot.accept("./number.js", () => {
  8. document.body.removeChild(document.getElementById("number"));
  9. number();
  10. });
  11. }

代码分割

打包出一个文件的代码过于庞大时,

  • 加载时间会很长
  • 更新的时间也同样会很长

解决方法

  • 多入口多出口打包,最后引入一个页面

这样打包后,如果页面逻辑发生变化只需要加载一个文件即可
例如loadsh文件就不需要再更新了

  • 也可以使配置项
    1. optimization: {
    2. splitChunks: {
    3. chunks: "all",
    4. },
    5. },
    自动帮你检测需要分割的代码

异步代码的分割

  1. function getComponent() {
  2. return import("lodash").then(({ default: _ }) => {
  3. var element = document.createElement("div");
  4. element.innerHTML = _.join(["CL", "LC"], "-");
  5. return element;
  6. });
  7. }
  8. getComponent().then(element => {
  9. document.body.appendChild(element);
  10. });

webpackChunkName魔法注释

如果想要给异步的文件重命名需要几个条件

  1. 添加魔法注释
  2. 使用babel-loader配合@babel/plugin-syntax-dynamic-import使用
  3. 配置SplitChunkPlugin插件选项
  1. // loader-module-rules
  2. {
  3. test: /\.js$/,
  4. exclude: /(node_modules|bower_components)/,
  5. use: {
  6. loader: "babel-loader",
  7. options: {
  8. presets: ["@babel/preset-env"],
  9. plugins: ["@babel/plugin-syntax-dynamic-import"],
  10. },
  11. },
  12. },
  1. optimization: {
  2. splitChunks: {
  3. chunks: "all",
  4. minSize: 30000,
  5. maxSize: 0,
  6. minChunks: 1,
  7. maxAsyncRequests: 5,
  8. maxInitialRequests: 3,
  9. automaticNameDelimiter: "~",
  10. name: false,
  11. cacheGroups: {
  12. vendors: false,
  13. default: false,
  14. },
  15. },
  16. },
  1. function getComponent() {
  2. return import(/* webpackChunkName:"lodash" */ "lodash").then(({ default: _ }) => {
  3. var element = document.createElement("div");
  4. element.innerHTML = _.join(["CL", "LC"], "-");
  5. return element;
  6. });
  7. }
  8. getComponent().then(element => {
  9. document.body.appendChild(element);
  10. });

SplitChunkPlugin选项解析

  • chunks
    • all:所有模块
    • async:异步模块
  • minSize:分割的最小文件大小,小于则不分割
  • maxSize:二次拆分模块,每个文件的最大
  • minChunks:最小引用次数,一个模块引用数小于配置则不会被分割
  • maxAsyncRequests: 同时加载的模块数
  • maxInitialRequests:入口文件加载的时候,最多的分割数
  • automaticNameDelimiter:文件连接符
  • name: false 自定义分割文件名
  • cacheGroups 缓存组,所有的模块分析完成之后,符合一个组的文件打包在一起
    • vendors 分割代码属于vendors这个组
      • name: “vendors”,都打包到vendors文件中去
      • priority:组分配优先级
      • reuseExistingChunk:模块间的相互引用时,已经被打包过的就不会再分组
    • default

      模块懒加载

      例如:点击的时候再去加载模块 ```javascript document.addEventListener(“click”, () => { getComponent().then(element => { document.body.appendChild(element); }); });
  1. 好处:页面展示速度加快
  2. <a name="KT52F"></a>
  3. ## chunck是什么
  4. 每一个拆分出的js文件都可以作为一个chunck
  5. <a name="FjQW6"></a>
  6. # Prefetching/Preloading
  7. 登录逻辑可以异步加载<br />但是如果用户点击后加载登录模块的话,可能展示速度会比较慢<br />是否可以再网络空闲的时候加载这个登录模块呢
  8. ```javascript
  9. document.addEventListener("click", () => {
  10. import("./handClick.js").then(({ default: func }) => {
  11. func();
  12. });
  13. });

点击后加载
image.png
添加魔法注释之后,空闲时加载

document.addEventListener("click", () => {
    import(/* webpackPrefetch:true */ "./handClick.js").then(({ default: func }) => {
        func();
    });
});

image.png
利用缓存机制

prefetch和preload的区别

  • prefetch会等待核心代码加载完成之后,页面空闲时再去加载异步模块
  • preload和主逻辑一同加载

所以prefetch更加适合异步模块的加载

css文件的代码分割

chunkFilename

如果一个css文件是以间接的形式引入,那么就会依赖chunkFilename配置

webpack与浏览器缓存

import _ from "lodash";
import $ from "jquery";

const dom = $("<div>");
dom.html(_.join(["L", "C"]), "");
$("body").append(dom);

打包之后,用户访问页面加载得到的js文件已经缓存到了本地浏览器中,如果代码更新但是文件名未更新,那么页面可能就不会重新渲染

解决这一问题的方式是在outPut输出需要的是加上.[contenthash].占位符,一旦文件修改hash值也会跟着改变

这样用户就只会更新改变过的文件

    output: {
        filename: "[name].[contenthash].js",
        chunkFilename: "[name].chunk.[contenthash].js",
        path: path.resolve(__dirname, "dist"),
    },

总结

  • 代码分割算是webpack中比较重要的操作吧
  • 我想说说代码分割的必要性吧,
    • 首先webpack默认配置会把项目打包合并到一个文件中去
    • 但是如果项目过大,那么最后打包出来的一个文件也就非常大
    • 这样就导致用户浏览器在加载以及更新的时候需要把这么大一个文件加载完毕后才能得到效果,非常浪费资源和时间
    • 这样就体现了代码分割的一个必要性了
  • 再来说说代码分割的一个技巧吧
    • 当项目中引用了很多三方库的时候,例如jQuery、lodash这类的库
    • 这些库的源码基本是不会变的,所以不必要每次都进行一个更新
    • 所以可以吧这些库按照引用的方式分割
  • 分割的方法
    • 首先是通过多入口多出口方式配置,将三方库的源码单独打包
    • 也可以通过optimization中的splitChunk选项来配置分割
      • 这其实是一个内置的plugin
      • 参数有chunks分隔方式,全部all或者异步async
      • minChunks按照最小引用次数来分割
      • minSize:小于这个选项的就不分割
      • 还可以使用cacheGroups 来分组分割,可以把同组的打包到一个文件中去,一般是vendors以及default两个组
      • 同时还可以配合魔法注释来实现分割文件的一个命名
  • 接下来说一下模块的一个懒加载吧
    • 懒加载也对应框架中的组件懒加载
    • 不过懒加载有时候也有一些问题
    • 比如我写了一个界面,界面中有一个登陆模块
    • 这个模块显然是不需要同时和首屏一起渲染的
    • 这个时候就可以使用模块的懒加载
    • 但是如果这个登录模块稍微复杂的话就可能会在点击登陆的时候加载很慢
    • 这个时候可以通过一个网络空闲加载的方式进行一个优化
    • 我使用的是一魔法注释,prefectching,也就是webpackPrefetch:设置weitrue,就会在首屏渲染之后,网络空闲的时候加载这个登陆模块
  • 最后说一下webpack和浏览器缓存之间存在的一个问题吧
    • 代码打包的时候,如果不手动改变出口文件的话,其实每次打包的文件是相同的名字,这就存在一个线上的问题了
    • 如果项目打包更新后,线上的文件名却没有进行一个修改如果用户不进行强制刷新的话,界面可能就会由于浏览器缓存而不进行改变
    • 解决这一问题的方式可以通过占位符【contenthash】来解决,这个占位符会根据不同的入口文件来对文件进行一个解析生成对应的一个hash值,只要不改动公共库的逻辑就不会改变他的哈希值,
    • contenthash是针对文件内容级别的而hash是针对项目级别的,每次更改一个文件所有文件的hash值都会重新生成