Webpack高级进阶学习笔记(暂停更新)

这部分笔记是观看尚硅谷webpack5高级进阶进行记录

本人目前还是学生,并无很多项目构建经验,所以此部分目前为初步了解学习阶段,待之后有项目经验后会再回来补足笔记

应是webpack基础学习的进阶,分析react与vue脚手架中wenpack的配置

本笔记更新至自定义webpack部分,暂停,待项目经验积累后在进行学习

除此笔记外大家可以看我其他笔记 :全栈笔记数据结构与算法编程_前端开发学习笔记编程_后台服务端学习笔记JavaNodejsJavaScript笔记ES6及后续版本学习笔记Vue笔记整合React笔记微信小程序学习笔记Chrome开发使用及学习笔记 以及许多其他笔记就不一一例举了

#目录

一、React中的配置分析

1、使用creact-react-app命令创建react项目

2、运行指令npm run eject:为了更好的学习与观察webpack在react中的配置,运行该命令

  1. 该命令会将react脚手架中隐藏的webpack配置暴露出来,而且`是不可逆`的,并使项目根目录多出两个文件目录:`config``scripts`,同时`package.json`的启动命令发生改变

Ⅰ-paths.js文件分析

1、appDirectory:项目根目录

2、resolveApp:生成绝对路径的方法

3、publicUrlOrPath:所有资源的公共访问路径 —>参数为/ 对应自己当前这个服务器的地址

4、moduleFileExtensions:定义文件拓展名,在这里定义的拓展名会被react解析到

5、resolveModule:拿到上面的文件拓展名,检查文件路径是否匹配,存在则解析

部分代码与注释

  1. 'use strict';
  2. const path = require('path');
  3. const fs = require('fs');
  4. const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
  5. //项目根目录
  6. const appDirectory = fs.realpathSync(process.cwd());
  7. //生成绝对路径的方法
  8. const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
  9. // 所有资源的公共访问路径: / -->`/`对应自己当前这个服务器的地址
  10. const publicUrlOrPath = getPublicUrlOrPath(
  11. process.env.NODE_ENV === 'development',
  12. //当你需要修改路径的时候去`package.json`进行修改,通常是不需要的
  13. require(resolveApp('package.json')).homepage,
  14. process.env.PUBLIC_URL
  15. );
  16. const buildPath = process.env.BUILD_PATH || 'build';
  17. //定义文件拓展名,在这里定义的拓展名会被react解析到
  18. const moduleFileExtensions = [
  19. 'web.mjs', 'mjs', 'web.js','js', 'web.ts','ts','web.tsx','tsx','json','web.jsx','jsx',
  20. ];
  21. // 解析模块的方法:
  22. //拿到上面的文件拓展名,检查文件路径是否匹配,存在则解析
  23. const resolveModule = (resolveFn, filePath) => {
  24. const extension = moduleFileExtensions.find(extension =>
  25. fs.existsSync(resolveFn(`${filePath}.${extension}`))
  26. );
  27. if (extension) {return resolveFn(`${filePath}.${extension}`); }
  28. return resolveFn(`${filePath}.js`);
  29. };
  30. // 暴露出去的路径
  31. module.exports = {
  32. dotenv: resolveApp('.env'),
  33. appPath: resolveApp('.'),
  34. ...
  35. };
  36. //将拓展名加至暴露出去的对象上暴露出去
  37. module.exports.moduleFileExtensions = moduleFileExtensions;

Ⅱ-start.js分析

用来运行开发环境配置

1、定义环境变量: ① process.env.BABEL_ENV ② process.env.NODE_ENV

2、useYarn:判断是否使用yarn

3、config = configFactory('development'):引入webpack的开发环境配置

4、checkBrowsers:检查当前使用的是什么浏览器

部分代码与注释

  1. 'use strict';
  2. // 定义环境变量:开发环境
  3. process.env.BABEL_ENV = 'development';
  4. process.env.NODE_ENV = 'development';
  5. // 捕获异常
  6. process.on('unhandledRejection', err => { throw err;});
  7. // 加载.env的环境变量:
  8. require('../config/env');
  9. //判断是否使用yarn
  10. const useYarn = fs.existsSync(paths.yarnLockFile);
  11. // 判断是否包含必要文件:publuc/index.html src/index.js 如果没有退出
  12. if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {process.exit(1);}
  13. // 定义默认端口号和域名
  14. const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
  15. const HOST = process.env.HOST || '0.0.0.0';
  16. const { checkBrowsers } = require('react-dev-utils/browsersHelper');
  17. //检查当前使用的是什么浏览器
  18. checkBrowsers(paths.appPath, isInteractive)
  19. .then(() => {
  20. // 检查端口号:检查当前端口号是否被占用,如果被占用自动加1
  21. return choosePort(HOST, DEFAULT_PORT);
  22. })
  23. //webpack的开发环境配置
  24. const config = configFactory('development');
  25. const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
  26. const appName = require(paths.appPackageJson).name;
  27. //判断是否使用typeScript
  28. const useTypeScript = fs.existsSync(paths.appTsConfig);
  29. const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
  30. // 创建编译器,将所有配置传进来
  31. const compiler = createCompiler({ appName,config, devSocket,urls,useYarn,useTypeScript,tscCompileOnError, webpack,});
  32. // 加载package.json中的prost配置
  33. const proxySetting = require(paths.appPackageJson).proxy;
  34. const proxyConfig = prepareProxy( proxySetting, paths.appPublic, paths.publicUrlOrPath );
  35. // 创建devServer的配置
  36. const serverConfig = createDevServerConfig(
  37. proxyConfig,
  38. urls.lanUrlForConfig
  39. );
  40. const devServer = new WebpackDevServer(compiler, serverConfig);
  41. // 启动服务
  42. devServer.listen(port, HOST, err => {
  43. if (err) {return console.log(err); }
  44. .....

Ⅲ-build.js

大致与开发环境相同,但是运行生产环境

Ⅳ-webpack.config.js

用来定义开发环境与生产环境的配置

部分代码与注释

  1. // 是否生成map文件
  2. // cross-env修改
  3. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
  4. // 是否内联runtime文件
  5. const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";
  6. // 最小转化base64的图片大小
  7. const imageInlineSizeLimit = parseInt( process.env.IMAGE_INLINE_SIZE_LIMIT || "10000");
  8. // 样式文件正则
  9. const cssRegex = /\.css$/;
  10. const cssModuleRegex = /\.module\.css$/;
  11. const sassRegex = /\.(scss|sass)$/;
  12. // 生成最终webpack开发或生产环境配置的函数
  13. module.exports = function (webpackEnv) {
  14. // 获取环境变量的方法
  15. // 加载.env文件的环境变量,REACT_APP_开头
  16. const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
  17. // 获取处理样式文件loader的函数
  18. const getStyleLoaders = (cssOptions, preProcessor) => {}
  19. // webpack配置对象
  20. return {
  21. mode: isEnvProduction ? "production" : isEnvDevelopment && "development",
  22. // 如果该变量为true(生产环境),则代码出错终止打包
  23. bail: isEnvProduction,
  24. devtool: isEnvProduction? shouldUseSourceMap? "source-map" // 生产环境
  25. : false: isEnvDevelopment && "cheap-module-source-map", // 开发环境
  26. }
  27. // 添加 /* filename */ 注释到输出的文件中
  28. pathinfo: isEnvDevelopment,
  29. // 默认 / ,可以通过package.json.homepage.
  30. publicPath: paths.publicUrlOrPath,
  31. // 启用压缩
  32. optimization: {
  33. minimize: isEnvProduction,
  34. minimizer: [new TerserPlugin({})]// 压缩js
  35. }
  36. // 是否内联runtime文件:少发一个请求
  37. isEnvProduction &&
  38. shouldInlineRuntimeChunk &&
  39. new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
  40. // 解析index.html中 &PULBLIC_URL%
  41. new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
  42. // 给ModuleNotFound更好提示
  43. new ModuleNotFoundPlugin(paths.appPath),
  44. // 定义环境变量
  45. new webpack.DefinePlugin(env.stringified),
  46. // 开发环境下:HMR功能
  47. isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
  48. // 文件路径:严格区分大小写
  49. isEnvDevelopment && new CaseSensitivePathsPlugin(),
  50. // 监视node_modules,一旦发生变化可以重启dev server
  51. // See https://github.com/facebook/create-react-app/issues/186
  52. isEnvDevelopment &&
  53. new WatchMissingNodeModulesPlugin(paths.appNodeModules),
  54. // 提取css成单独文件
  55. isEnvProduction &&
  56. new MiniCssExtractPlugin({})
  57. }

二、Vue中的配置分析

将基于脚手架4.x版本的vue2.x配置进行分析,大致内容与react无太大区别

1、卸载老版本vuecli(1.x, 2.x):npm uninstall vue-cli -g或者yarn global remove vue-cli

2、安装最新的vuecli:npm install -g @vue/cli

3、vue create project-name,选择创建vue2.x,vue2与3版本的weback配置差不多

4、vue并没有提供将webpack配置直接暴露出来的命令,但是提供了另外一种方法让我们能审查vue里面的配置,通过指令将单独的配置单独打包成单独的文件

  1. Ⅰ-`vue inspect --mode=development > webpack.dev.js` :将webpack的开发环境单独打包
  2. Ⅱ-`vue inspect --mode=production > webpack.prod.js` :将webpack的生产环境单独打包
  3. Ⅲ-生成后的文件会报错,想要取消报错,在最前面用`module.exports=`将其暴露出去,就不会报错了

三、loader

Ⅰ-编写一个简单的loader

loader本质上是一种函数,此处创建一个js编写loader

  1. //loader1.js
  2. module.exports = function (content, map, meta) {
  3. console.log(content,"------------------------------------");
  4. return content
  5. }

1、如不配置loader解析规则,默认路径是node_modules,所以平时使用style-loader等都不用加路径

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. module: {
  4. rules: [
  5. {
  6. test: /\.js$/,
  7. loader: 'loader1',//当配置了liader解析规则后写法
  8. // loader:path.resolve(__dirname,'loaders','loader1') //不写resolveLoader配置的写法
  9. }
  10. ]
  11. },
  12. // 配置loader解析规则,默认路径是`node_modules`
  13. resolveLoader: {
  14. modules: [
  15. 'node_modules',
  16. path.resolve(__dirname, 'loaders')
  17. ]
  18. }
  19. }

Ⅱ-loader的执行顺序

1、loader是从上往下编译,编译结束后运行的顺序是从下往上,从右往左执行

2、当你有写loader想要先行运行的话,可以加在pitch方法上,这样在编译时就会调用,且从上往下执行

  1. module.exports = function (content, map, meta) {
  2. console.log(111111,"------------------------------------");
  3. return content
  4. }
  5. module.exports.pitch = function () {//编译时从上往下调用
  6. console.log('pitch 111');
  7. }

Ⅲ-同步&异步loader

1、this.callback():同步的方法 —>可以替代return

  1. module.exports = function (content, map, meta) {
  2. console.log(111111,"------------------------------------");
  3. this.callback(null, content, map, meta);
  4. //是否有错误,需要传递的内容(处理后的内容) ,可选参数,可选参数
  5. }

2、this.async():异步的方法

  1. 使用`this.async()`方法会使整个loader停住,只有当你再次调用`callback`方法才会继续执行,整体性能会好过同步,`推荐使用`
  1. // 异步loader
  2. module.exports = function (content, map, meta) {
  3. console.log(222);
  4. const callback = this.async();
  5. setTimeout(() => {
  6. callback(null, content);
  7. }, 1000)
  8. }

Ⅳ-获取&校验loader的options

1、需要下载依赖:npm i loader-utils -D

2、需要下载依赖:schema-utils

  1. //webpack.config.js传入
  2. { loader: 'loader3',
  3. options: {
  4. name: 'jack',
  5. age: 18 //当校验文件追加属性调为false,将会报错
  6. }
  7. }
  8. ------------------------------------------------------------------------------------
  9. //loader3.js
  10. // loader本质上是一个函数
  11. const { getOptions } = require('loader-utils');
  12. const { validate } = require('schema-utils');
  13. const schema = require('./schema');
  14. module.exports = function (content, map, meta) {
  15. // 获取options
  16. const options = getOptions(this);
  17. console.log(333, options);
  18. // 校验options是否合法
  19. validate(schema, options, {
  20. name: 'loader3'
  21. })
  22. return content;
  23. }

校验文件:定义校验规则

  1. {
  2. "type": "object",//指定options的类型
  3. "properties": { //定义options里面有什么属性
  4. "name": { //定义有name类型
  5. "type": "string",
  6. "description": "名称~" //定义描述,可以随便写
  7. }
  8. },
  9. "additionalProperties": false //代表是否可以运行追加其他属性
  10. }

Ⅴ-自定义babel-loader

自定义babel-loader,并不是工作中的babel配置

webpack.config.js配置

  1. module.exports = {
  2. module: {
  3. rules: [{
  4. test: /\.js$/,
  5. loader:'babelLoader',
  6. options:{ presets:['@babel/preset-env'] }
  7. }]
  8. },
  9. // 配置loader解析规则,默认路径是`node_modules`
  10. resolveLoader: { modules: [ 'node_modules',path.resolve(__dirname, 'loaders')]}
  11. }

babelLoader.js —>中间大写是防止与自带的babelloader冲突

  1. const { getOptions } = require('loader-utils');
  2. const { validate } = require('schema-utils');
  3. const babel = require('@babel/core');
  4. const util = require('util');
  5. const babelSchema = require('./babelSchema.json');
  6. //babel.transfrom用来编译代码的方法,是一个普通异步方法
  7. //util.promisify会将普通异步方法转换成基于promise的异步方法
  8. const transform=util.promisify(babel.transform)
  9. module.exports = function (content, map, meta) {
  10. //获取loader的options配置
  11. const options = getOptions(this) || {};
  12. //校验babel的options的配置
  13. validate(babelSchema,options,{
  14. name:'Babel Loader'
  15. })
  16. //创建异步
  17. const callback=this.async();
  18. //使用babel编译代码
  19. transform(content,options)
  20. .then(({code,map})=>callback(null,code,map,meta))
  21. .catch((e)=>callback(e))
  22. }

校验文件:babelSchema.json

  1. {
  2. "type": "object",
  3. "properties": {
  4. "presets": {
  5. "type": "array"
  6. }
  7. },
  8. "addtionalProperties": true
  9. }

四、plugins

Ⅰ-tabaple介绍与使用

安装tapable:npm install tapable -D

初始化hooks容器 2.1 同步hooks,任务会依次执行:SyncHook、SyncBailHook 2.2 异步hooks,异步并行:AsyncParallelHook,异步串行:AsyncSeriesHook

往hooks容器中注册事件/添加回调函数

触发hooks

启动文件:node tapable.test.js

  1. const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require('tapable');
  2. class Lesson {
  3. constructor() {
  4. // 初始化hooks容器
  5. this.hooks = {
  6. // 同步hooks,任务回依次执行
  7. // go: new SyncHook(['address'])
  8. // SyncBailHook:一旦有返回值就会退出~
  9. go: new SyncBailHook(['address']),
  10. // 异步hooks
  11. // AsyncParallelHook:异步并行
  12. // leave: new AsyncParallelHook(['name', 'age']),
  13. // AsyncSeriesHook: 异步串行
  14. leave: new AsyncSeriesHook(['name', 'age'])
  15. }
  16. }
  17. tap() {
  18. // 往hooks容器中注册事件/添加回调函数
  19. this.hooks.go.tap('class0318', (address) => {
  20. console.log('class0318', address);
  21. return 111;
  22. })
  23. this.hooks.go.tap('class0410', (address) => {
  24. console.log('class0410', address);
  25. })
  26. this.hooks.leave.tapAsync('class0510', (name, age, cb) => {
  27. setTimeout(() => {
  28. console.log('class0510', name, age);
  29. cb();
  30. }, 2000)
  31. })
  32. this.hooks.leave.tapPromise('class0610', (name, age) => {
  33. return new Promise((resolve) => {
  34. setTimeout(() => {
  35. console.log('class0610', name, age);
  36. resolve();
  37. }, 1000)
  38. })
  39. })
  40. }
  41. start() {
  42. // 触发hooks
  43. this.hooks.go.call('c318');
  44. this.hooks.leave.callAsync('jack', 18, function () {
  45. // 代表所有leave容器中的函数触发完了,才触发
  46. console.log('end~~~');
  47. });
  48. }
  49. }
  50. const l = new Lesson();
  51. l.tap();
  52. l.start();

Ⅱ- compiler钩子

工作方式:异步串行执行,因此下面代码输出顺序如下: 1.1 emit.tap 111 1.2 1秒后输出 emit.tapAsync 111 1.3 1秒后输出 emit.tapPromise 111 1.4 afterEmit.tap 111 1.5 done.tap 111

tapAsync和tapPromise表示异步

  1. class Plugin1 {
  2. apply(complier) {
  3. complier.hooks.emit.tap('Plugin1', (compilation) => {
  4. console.log('emit.tap 111');
  5. })
  6. complier.hooks.emit.tapAsync('Plugin1', (compilation, cb) => {
  7. setTimeout(() => {
  8. console.log('emit.tapAsync 111');
  9. cb();
  10. }, 1000)
  11. })
  12. complier.hooks.emit.tapPromise('Plugin1', (compilation) => {
  13. return new Promise((resolve) => {
  14. setTimeout(() => {
  15. console.log('emit.tapPromise 111');
  16. resolve();
  17. }, 1000)
  18. })
  19. })
  20. complier.hooks.afterEmit.tap('Plugin1', (compilation) => {
  21. console.log('afterEmit.tap 111');
  22. })
  23. complier.hooks.done.tap('Plugin1', (stats) => {
  24. console.log('done.tap 111');
  25. })
  26. }
  27. }
  28. module.exports = Plugin1;

此部分剩余部分,暂时跳过,并无太多实践,以后再来补足

五、自定义webpack

Ⅰ-webpack执行流程

1、初始化 Compiler:webpack(config) 得到 Compiler 对象

2、开始编译:调用 Compiler 对象 run 方法开始执行编译

3、确定入口:根据配置中的 entry 找出所有的入口文件。

4、编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行编译,再找出该模块依赖的模块,递归直到所有模块被加载进来

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

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

7、输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

Ⅱ-准备工作

1、创建文件夹myWebpack

2、创建src—>(add.js / count.js / index.js),写入对应的js代码

3、创建config—>webpack.config.js写入webpack基础配置(entry和output)

4、创建lib文件夹,里面写webpack的主要配置

5、创建script—>build.js(将lib文件夹下面的myWebpack核心代码和config文件下的webpack基础配置引入并调用run()函数开始打包)

6、为了方便启动,控制台通过输入命令 npm init -y拉取出package.json文件,修改文件中scripts部分为"build": "node ./script/build.js"表示通过在终端输入命令npm run build时会运行/script/build.js文

7、如果需要断点调试:在scripts中添加"debug": "node --inspect-brk ./script/build.js"表示通过在终端输入命令npm run debug时会调试/script/build.js文件中的代码

下面部分暂停学习,转回React系统学习 2021/3/6