1. 首页

最后一次Webpack 深入再深入总结

前言

本文为作者第二次专门对 Webpack 的知识点进行深入和实践,根据理解和实践的结果进行总结的;

文章内容参考书籍《深入浅出 Webpack》,因为该书籍基于 Webpack 3.4.0 版本,本文的实践基于 Webpack 4.28.2 版本,所以也踩了不少由于模块版本问题出现的坑,已经汇总到第 6 章节 踩坑汇总,大家记得避免踩坑;也印证了那句哲理:纸上得来终觉浅,绝知此事要躬行 …

博客 github 地址为:github.com/fengshi123/… ,汇总了作者的所有博客,也欢迎关注及 star ~

本文实践 demo 的 github 地址

一、Webpack 原理

1、构建作用

构建工具就是将源代码转换成可执行的 JavaScript、CSS、HTML 代码,包括以下内容:

  • 代码转换:将 TypeScript 编译成 JavaScript、将 SCSS 编译成 CSS 等;

  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等;

  • 代码分割:提取多个页面的公共代码,提取首屏不需要执行部分的代码,让其异步加载;

  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要通过构建功能将模块分类合并成一个文件;

  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器;

  • 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过;

  • 自动发布:更新代码后,自动构建出线上发布代码并传输给发布系统;

2、核心概念

Webpack 有以下几个核心概念:

  • Entry :入口,Webpack 执行构建的第一步将从 entry 开始,可抽象成输入;

  • Module:模块,配置处理模块的规则;在 Webpack 里一切皆模块,一个模块对应一个文件;Webpack 会从配置的 Entry 开始递归找出所有依赖的模块;

  • Loader:模块转换器,用于将模块的原内容按照需求转换成新内容;

  • Resolve:配置寻找模块的规则;

  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播对应的事件,插件可以监听这些事情的发生,在特定的时机做对应的事情;

  • Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果;

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

3、流程概述

(1)初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;

(2)开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,通过执行对象的 run 方法开始执行编译;

(3)确定入口:根据配置中的 entry 找出所有入口文件;

(4)编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;

(5)完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容及它们之间的依赖关系;

(6)输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再将每个 Chunk 转换成一个单独的文件加入输出列表中,这是可以修改输出内容的最后机会;

(7)输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,将文件的内容写入文件系统中;

在以上过程中,Webpack 会在特定的时间点广播特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果;

二、Webpack 配置

1、Webpack 项目初始化

1、新建 Web 项目

新建一个目录,再进入项目根目录执行  npm init 来初始化最简单的采用了模块化开发的项目;最终生成 package.json 文件;


$ npm init

2、安装 Webpack 到本项目

(1)查看 Webpack 版本

运行以下命令可以查看 Webpack 的版本号


$ npm view webpack versions

(2)安装 Webpack

可以选择(1)步骤罗列得到的 Webpack 版本号,也可以安装最新稳定版、最新体验版本,相关命令如下所示,我选择安装 4.28.2 版本(没有为什么,就想装个 4.x 的版本);


// 安装指定版本 npm i -D webpack@4.28.2 // 安装最新稳定版 npm i -D webpack // 安装最新体验版本 npm i -D webpack@beta

(3)安装 Webpack 脚手架

需要安装 Webpack 脚手架,才能在命令窗口执行 Webpack 命令,运行以下命令安装 Webpack 脚手架;


$ npm i -D webpack-cli

3、使用 Webpack

使用 Webpack 构建一个采用 CommonJS 模块化编写的项目;

(1)新建页面入口文件 index.html


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Webpack</title> </head> <body> <!--导入 Webpack 输出的 JavaScript 文件--> <script src="./dist/bundle.js"></script> </body> </html>

(2)新建需要用到的 JS 文件

show.js 文件

// 操作 DOM 元素,把 content 显示到网页上
function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 通过 CommonJS 规范导出 show 函数
module.exports = show;

main.js 文件

// 通过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');

(3)新建 Webpack 配置文件 webpack.config.js

const path = require('path');

module.exports = {
  // JavaScript 执行入口文件
  entry: './main.js',
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 输出文件都放到 dist 目录下
    path: path.resolve(__dirname, './dist'),
  },
};

(4)执行 webpack 命令进行构建

在 package.json 文件中配置编译命令,如下所示:


"scripts": { "build": "webpack --config webpack.config.js", },

执行以下命令进行项目的 Webpack 编译,成功后会在项目根目录下生成编译目录 dist ;


$ npm run build

(5)运行 index.html

编译成功后,我们用浏览器打开 index.html 文件,能看到页面成功显示 “Hello Webpack”;

2、Loader 配置

本节通过为之前的例子添加样式,来尝试使用 Loader;

(1)新建样式文件 main.css


\#app{ text-align: center; color:'#999'; }

(2)将 main.css 文件引入入口文件 main.js 中,如下所示:

// 通过 CommonJS 规范导入 CSS 模块
require('./main.css');

// 通过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');

(3)Loader 配置

以上修改后去执行 Webpack 构建是会报错的,因为 Webpack 不原生支持解析 CSS 文件。要支持非 JavaScript 类型的文件,需要使用 Webpack 的 Loader 机制;

(3.1)运行以下命令,安装 style-loader 和 css-loader,其中:

  • css-loader 用于读取 CSS 文件;
  • style-loader 把 CSS 内容注入到 JavaScript 中;

$ npm i -D style-loader css-loader

(3.2)进行以下配置

module: {
  rules: [
    {
      // 用正则去匹配要用该 loader 转换的 CSS 文件
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
    },
  ];
}

(4)查看结果

编译后,刷新 index.html ,查看刚刚的样式 loader 已经起作用;

1

3、Plugin 配置

(1)安装样式提取插件 extract-text-webpack-plugin


$ npm i -D extract-text-webpack-plugin@next

(2)plugin 文件配置如下

  module:{
    rules:[
      {
        // 用正则去匹配要用该 loader 转换的 CSS 文件
        test:/\.css$/,
        use:ExtractTextPlugin.extract({
          use:['css-loader']
        }),
      }
    ]
  },
  plugins:[
    new ExtractTextPlugin({
       // 从 .js 文件中提取出来的 .css 文件的名称
       filename:`[name]_[hash:8].css`
    }),
  ]

(3)查看结果

通过以上配置后,执行 Webapack 的执行命令,发现在 dist 目录下,生成对应的 css 文件;存在的坑点:

  • 我们需要手动将生成的 css 文件引入到 index.html 中;
  • 修改 css 文件后,会生成新的 css 文件,原先的不会删除;

4、使用 DevServer

(1)执行以下命令安装 webpack-dev-server


$ npm i -D webpack-dev-server

在 package.json 中配置启动命令


"scripts": { "build": "webpack --config webpack.config.js", "dev": "webpack-dev-server", },

运行命令后,就可以启动 HTTP 服务


$ npm run dev

启动结果如下所示,我们可以通过 http://localhost:8080/ 访问我们的 index.html 的 demo

1

(2)实时预览

我们在运行命令后面添加参数 –watch 实现实时预览,配置如下所示:


"scripts": { "dev": "webpack-dev-server --watch" },

然后我们修改 main.js 的传入参数,发现并不能实时预览,也没有报错!!! why?

踩坑:

在 index.html 中需要将 js 的路径修改为:

<script src="bundle.js"></script>

而不能是之前的(因为这个是编译生成的,并不是通过 devServer 生成放在内存的)


<\script src="./dist/bundle.js"></script>

(3)模块热替换

可以通过配置 — hot 进行模块热替换;

三、Webpack 优化

关于优化的实践之前有进行过实践了,这里不再累述,感兴趣的童鞋可以查看作者写的另一篇文章《Vue 项目 Webpack 优化实践,构建效率提高 50%

四、编写 Loader

1、Loader 要点总结

(1)Loader 为模块转换器,用于将模块的原内容按照需求转换成新内容;

(2)Loader 的职责是单一的,只需要完成一种转换,遵守单一职责原则;

(3)Webpack 为 Loader 提供了一系列 API 供 Loader 调用,例如:

  • loader-utils.getOptions( this ) 获取用户传入的 options,
  • this.callback( ) 自定义返回结果,
  • this.async( ) 支持异步操作;
  • this.context 当前文件所在的目录;
  • this.resource 当前处理文件的完整请求路径;
  • 其它等等

2、编写 loader 源码

手写一个 loader 源码,其功能是将 /hello/gi 转换成 HELLO,当然这个 loader 其实没啥实际意义,纯碎是为了写 loader 而写 loader;当然如果你实际业务有需要编写 loader 需求,那就要反思这个业务的合理性,因为庞大的社区,一般合理的需求都能找到对应的 loader。

(1)源码编写

在原有的项目底下,新建目录 custom-loader 作为我们编写 loader 的名称,执行 npm init 命令,新建一个模块化项目,然后新建 index.js 文件,相关源码如下:

function convert(source) {
  return source && source.replace(/hello/gi, 'HELLO');
}

module.exports = function(content) {
  return convert(content);
};

(2)Npm link 模块注册

正常我们安装 Loader 是从 Npm 公有仓库安装,也即将 Loader 发布到 Npm 仓库,然后再安装到本地使用;但是我们可以使用 Npm link 做到在不发布模块的情况下,将本地的一个正在开发的模块的源码链接到项目的 node_modules 目录下,让项目可以直接使用本地的 Npm 模块;

在 custom-loader 目录底下,运行以下命令,将本地模块注册到全局:


$ npm link

成功结果如下:

1

然后在项目根目录执行以下命令,将注册到全局的本地 Npm 模块链接到项目的 node_modules 下:


$ npm link custom-loader

成功结果如下,并且在 node_modules 目录下能查找到对应的 loader;

1

3、Webpack 中配置编写的 loader

该配置跟第一章节的 Webpack 配置并没有任何区别,这里不再详述,配置参考如下:

module: {
  rules: [
    {
      test: /\.js/,
      use: ['custom-loader'],
      include: path.resolve(__dirname, 'show'),
    },
  ];
}

执行运行 or 编译命令,就能看到我们的 loader 起作用了。

五、编写 Plugin

Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果,这条生产线上的每个处理流程的职责都是单一的,多个流程之间存在依赖关系,只有在完成当前处理后才能提交给下一个流程去处理。插件就像生产线中的某个功能,在特定的时机对生产线上的资源进行处理。

Webpack 通过  Tapable  来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。

1、Plugin 要点总结

  • Webpack 在编译过程中,会广播很多事件,例如 run、compile、done、fail 等等,可以查看官网;
  • Webpack 的事件流机制应用了观察者模式,我们编写的插件可以监听 Webpack 事件来触发对应的处理逻辑;
  • 插件中可以使用很多 Webpack 提供的 API,例如读取输出资源、代码块、模块及依赖等;

2、编写 Plugin 源码

手写一个 plugin 源码,其功能是在 Webpack 编译成功或者失败时输出提示;当然这个 plugin 其实没啥实际意义,纯碎是为了写 plugin 而写 plugin;当然如果你实际业务有需要编写 plugin 需求,那就要反思这个业务的合理性,因为庞大的社区,一般合理的需求都能找到对应的 plugin。

(1)源码编写

在原有的项目底下,新建目录 custom-plugin 作为我们编写 plugin 的名称,执行 npm init 命令,新建一个模块化项目,然后新建 index.js 文件,相关源码如下:

class CustomPlugin {
  constructor(doneCallback, failCallback) {
    // 保存在创建插件实例时传入的回调函数
    this.doneCallback = doneCallback;
    this.failCallback = failCallback;
  }
  apply(compiler) {
    // 成功完成一次完整的编译和输出流程时,会触发 done 事件
    compiler.plugin('done', stats => {
      this.doneCallback(stats);
    });
    // 在编译和输出的流程中遇到异常时,会触发 failed 事件
    compiler.plugin('failed', err => {
      this.failCallback(err);
    });
  }
}
module.exports = CustomPlugin;

(2)Npm link 模块注册

跟 Loader 注册一样,我们使用 npm link 进行注册;

在 custom-plugin 目录底下,运行以下命令,将本地模块注册到全局:


$ npm link

然后在项目根目录执行以下命令,将注册到全局的本地 Npm 模块链接到项目的 node_modules 下:


$ npm link custom-plugin

如果一切顺利,可以在 node_modules 目录下能查找到对应的 plugin;

3、Webpack 中配置编写的 plugin

该配置跟第一章节的 Webpack 配置并没有任何区别,这里不再详述,配置参考如下:


plugins:[ new CustomPlugin( stats => {console.info('编译成功!')}, err => {console.error('编译失败!')} ), ],

执行运行 or 编译命令,就能看到我们的 plugin 起作用了。

六、踩坑汇总

1、css-loader 以下配置

rules: [
  {
    // 用正则去匹配要用该 loader 转换的 CSS 文件
    test: /\.css$/,
    use: ['style-loader', 'css-loader?minimize'],
  },
];

报以下错误:


- options has an unknown property 'minimize'. These properties are valid: object { url?, import?, modules?, sourceMap?, importLoaders?, localsConventio n?, onlyLocals?, esModule? }

原因:

minimize 属性在新版本已经被移除,

解决:

先去掉 minimize 选项;

2、ExtractTextPlugin 编译以下错误:

1

原因:

extract-text-webpack-plugin 版本号问题

参考链接:github.com/webpack/web…

解决:

重新安装 extract-text-webpack-plugin


$ npm i -D extract-text-webpack-plugin@next

3、修复第 2 个坑之后,ExtractTextPlugin 编译继续报以下错误:

1

原因:

不存在 contenthash 这个变量

解决:

更改 extract-text-webpack-plugin 的配置:

plugins: [
  new ExtractTextPlugin({
    // 从 .js 文件中提取出来的 .css 文件的名称
    filename: `[name]_[hash:8].css`,
  }),
];
复制代码;

4、添加 HappyPack 后,编译 CSS 文件时报以下错误:

1

原因:

css-loader 版本的问题

解决:

重新安装 css-loader@3.2.0

七、总结

本文主要基于 Webpack 的作用、核心概念、流程,Webpack 的基础配置,Webpack 优化,编写 Loader,编写 Plugin ,从理论到实践,从基础到较难,对 Webpack 进行总结掌握,希望对你也有帮助。还是那句话:纸上得来终觉浅,绝知此事要躬行 …,如果你没有手敲过,一定要多动动手 !

本文实践 demo 的 github 地址

作者:我是你的超级英雄
链接:https://juejin.im/post/5e17c1dd6fb9a02fd67e9990

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:最后一次Webpack 深入再深入总结

链接:https://www.javascriptc.com/3930.html

« 从0到1搭建TypeScript+webpack4开发环境
HTTP缓存总结»
Flutter 中文教程资源

相关推荐

QR code