part2-m2

  • 本阶段主要以前端工程化为主题,分别从脚手架工具、自动化构建、模块化开发、规范化标准四个维度介绍前端工程化具体该如何落地、如何实践,以此应对复杂前端应用的开发和维护过程,提高开发者的工作效率,降低项目维护成本,从而更好地适应大前端时代下的前端开发工作。

模块二 模块化开发与规范化标准

  • 此模块中会介绍当下前端开发过程中最重要的开发范式:模块化,我们会介绍模块化在前端行业的演进过程、如何实现模块化开发以及 Webpack 打包工具的使用和核心工作原理;另外我们还会为你介绍几个其它的打包工具;最后通过落实规范化,通过工具保证团队的代码质量,让整个团队的代码更漂亮。
任务一:模块化开发
  1. 模块化概述
  • 模块化开发:当前最重要的前端开发范式
  • 内容概要
    • 模块化演变过程
    • 模块化规范
    • 常用的模块化打包工具
    • 基于模块化工具构建现代Web应用
    • 打包工具的优化技巧
  1. 模块化的演进过程
  • 第一阶段:基于文件划分的形式
    • 污染全局作用域
    • 命名冲突问题
    • 无法管理模块依赖关系
    • 早期模块化完全依靠约定
  • 第二阶段:命名空间方式
    • 每个模块只暴露一个对象,其它所有属性都挂载在这个对象上
  • 第三阶段: IIEF 需要暴露的成员挂载到全局对象上,实现了私有成员,通过传入参数,可以很清楚的知道依赖关系
  • 都是通过script标签手动的引入。模块加载不受代码控制
  1. 模块化规范的出现
  • 模块化标准 + 模块加载器
  • Commonjs 规范 Nodejs提出的标准
    • 一个就是一个模块
    • 每个模块都有单独的作用域
    • 通过 module.exports导出成员
    • 通过require函数载入模块
    • CommonJS是以同步模式加载模块,因为Node是在启动时加载模块,执行过程中不需要加载
    • 如果在浏览器端使用会出现问题,每一次加载都会出现大量的同步请求
  • AMD 异步模块定义规范
    • Require.js 实现了AMD这个规范,本身是个非常强大的模块加载器
    • 目前绝大部分第三份库都支持AMD规范
    • AMD 使用起来相对复杂
    • 如果模块划分过细,那么模块JS文件请求频繁,导致页面效率低下
  • Sea.js + CMD 淘宝 类似CommonJS规范,后来这种方式也被Require.js兼容了
  1. 模块化标准规范
  • 浏览器: ES Modules ; Node: Commonjs
    • CommonJS in Node.js 内置的模块系统
    • ES Modules ECMAScript2015(ES6)中定义的模块规范,在语言层面实现了模块化
  1. ES Modules 特性
  • 通过给Script 添加type=module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码
    • ESM 自动采用严格模式, 忽略 ‘use strict’
    • 每个 ES Module 都是运行在单独的私有作用域中
    • ESM 是通过CORS 的方式请求外部JS 模块的
    • CORS 不支持文件的形式,必须通过http server 的方式去访问(开发环境不会遇到,因为都是通过 http server方式)
    • ESM 的script 标签会延迟执行脚本
  1. ES Modules 导入和导出
  • export 导出 import 导入
  • browser-sync . --files **/*.js
  • export { name as fooName }
  1. ES Modules 导入导出的注意事项
  • export 后面必须带花括号{},这是一种固定的语法,并不是字面量。如果要带字面量,export default {}
    • 导出的是这个成员的引用,并不是复制,给的只是引用关系
    • 导入的成员是只读的,外部并不能修改
    • 外部导入的值会受模块内部对该值修改的影响
  • import {}也不是解构,只是一个固定的语法
  1. ES Modules 导入用法
  • import { name } from ‘./module.js’ from后面的文件路径和后缀不能省略
    • ./ 也不能省略,省略会认为是加载第三方的模块
    • 也可以使用 /绝对路径,从网站根目录开始
    • 也可以通过完整 url 加载模块
    • import {}from ‘./module.js’ 如果花括号为空,只会去执行这个模块,不会提取其中的成员,简写: import ‘./module.js’
    • import * as mod from ‘./module.js’ 导出成员过多,通过* + as的方式放到一个变量中
    • import 是一个声明,只能出现在最顶层
    • 通过 import(’./module.js’) 这个函数可以动态的加载模块,返回的是一个promsie
    • import abc, {name, age} from ‘./module.js’ 逗号左边的导出的abc是默认成员
  1. ES Modules 导出导入成员
  • 可以在当前模块直接导出导入的成员,直接在当前模块把 import 改成 export
    • import { name } from ‘./module.js’ 》 export { name } from ‘./module.js’
    • 在index 中会用到,把本项目中散落的模块暴露出去,方便外部使用
  1. ES Modules in Browser 浏览器环境Polyfill
  • browser-es-module-loader 只要在网页中引用这个文件,就可以解决浏览器不兼容ES Modules的问题
  • 针对npm 的模块,可以通过 unpkg.com 这个网站提供的cdn服务拿到下面所有的js文件
    • unpkg.com/包名
    • 需要给为支持Polyfill引入 的scirpt 标签 加上 nomodule 属性,不然在支持module的浏览器中,模块会执行两次
    • 原理是在运行阶段,动态的解析脚本,效率会非常差,不适合开发环境用
  1. ES Modules in Node.js 支持情况
  • Node.js 8.5 后就开始以实验的方式支持 ES Modules
    • nodejs/node 》 cjs + esm = 用于加载Commonjs 模块
    • 需要把模块文件后缀由.js改成.mjs
    • 执行时需要加允许实验的特性: node --experimental-modules index.mjs
    • 无论是原生模块还是第三方模块都可以通过 import 方式导入
    • 第三方模块只支持默认导出,不支持{}的方式,因为import {}不是解构
    • 内置模块兼容了ESM 的提取成员方式。导出了多个对象,再做了默认导出
  1. ES Modules in Node.js 与 CommonJS 模块交互
  • ES Module 中可以导入 CommonJS 模块
    • 不能直接提取成员,注意import 不是解构导出对象
    • CommonJS 模块始终只会导出一个默认成员
  • 不能在CommonJS 模块中通过 require 载入 ES Module
  1. ES Modules in Node.js - 与 CommonJS 差异
  • ESM 中没有CommonJS 中的那些模块全局成员了
    • CommonJS 中的模块全局成员:require 加载模块函数 module 模块对象 exports 导出对象别名 __filename 当前文件的绝对路径 __dirname 当前文件所在目录,看源码并不是全局对象,而是伪全局对象,包装的形参
    • import.meta.url 拿到的就是当前文件的工作地址
      • import { fileURLToPath } from ‘url’
      • import { dirname } from ‘path’
      • const __filename = fileURLToPath(import.meta.url)
      • const __dirname = dirname(__filename)
  1. ES Modules in Node.js 新版本进一步支持ESM
  • nvm use 12.10.0
  • 在 package.json 中添加 type: ‘module’,就不用修改为.mjs的扩展名了
  • 但是如果还想要使用CommonJS,就需要给当前文件修改为.cjs 为扩展名
    • node --experimental-modules index.cjs
  1. ES Modules in Node.js 兼容方案
  • 早期node版本,可以使用Babel进行兼容,最主流的一款编译器,可以把使用了新特性的代码编译成当前环境支持的代码
    • yarn add @babel/node @baabel/core @babel/preset-env --dev
    • babel 基于插件实现的,核心模块并不会转换代码
    • @babel/preset-env 是一个插件的集合
    • yarn babel-node index.js --presets=@babel/preset-env
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYmqvDdU-1626104766415)(…/img/1625201409619.jpg)]
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9TCCmYIr-1626104766418)(…/img/1625201487046.jpg)]
    • 也可以通过添加 .babelrc 文件 》 { “presets”: ["@babel/preset-env"] } 》 yarn babel-node index.js
    • yarn add @babel/plugin-transform-modules-commonjs --dev 实际转换的插件
    • .babelrc 文件 》 { “plugin”: ["@babel/plugin-transform-modules-commonjs "] } 》 yarn babel-node index.js 需要用到哪个引哪个
任务二:Webpack 打包
  1. 模块打包工具的由来
  • ES Modules 存在环境兼容问题
  • 通过模块化划分模块文件过多,网络请求频繁
  • 所有的前端资源都需要模块化
  • 开发阶段 》编译 打包 》 生产环境
  • 对于整个前端应用而言的模块化解决方案,不单单是JavaScript
    • 新特性代码编译
    • 模块化JavaScript打包
    • 支持不同类型的模块资源
  1. 模块打包工具概要
  • webpack 模块打包器(Module bundler)
    • 有兼容问题的代码,可以在打包阶段通过模块加载器(Loader)进行编译转换
    • 代码拆分(Code Solitting)将应用中所有的代码按照需要去打包。渐进式加载:首次先加载需要立即执行的代码,后续再加载其它代码
    • 资源模块(Asset Module) 支持以模块化的方式加载任意资源
    • 打包工具解决的是前端整体的模块化
  1. Webpack 快速上手
  • yarn init --yes
  • yarn add webpack webpack-cli --dev
  • yarn webpack --version
  • yarn webpack 打包
  • packaage.json “scripts”: { “build”: “webpack” } yarn build
  1. Webpack 配置文件
  • Webpack 4以后的版本支持0配置打包

    • 按照约定 ‘src/index.js’ > ‘dist/main.js’
    • webpack.config.js 运行在node环境,按照CommonJS规范编写
    const path = require('path')
    module.exports = {
        entry: './src/main.js',
        output: {
            filename: 'bundle.js',
            path: path.join(__dirname, 'output')
        }
    }
    
  1. Webpack 工作模式
  • 针对于不同环境的几组预设配置(工作模式)
  • 默认是打包上线
  • yarn webpack --mode development
  • yarn webpack --mode none 运行最原始的打包,不会做如何处理
  • 也可以在配置文件中添加配置属性 mode: ‘development’
  1. Webpack 打包结果运行原理
  • 把 mode:‘none’ 可以看到webpack 打包的原始结果
    • 有一个IEEF 自执行函数把全部代码包裹
    • 一个数组,里面放着所有模块
    • 一个模块管理的函数,在函数上挂载三个方法
      • .d: 如果 需要导出的对象这个属性是自身的且模块列表(exports)中没有,则加入exports)
      • .o: 判断对象自身(非继承)是否有这个属性
      • .r: 把每个模块导出结果都加一个__esModule,标记为 ES Module 规范
    • 最后是入口函数自调用
  1. Webpack 资源模块加载
  • 默认default-loader只能处理js文件,处理其它文件需要加载other-loader
    • yarn add css-loader --dev 只会把css打包成一个模块
    • yarn add style-loader --dev 会把打包好的css模块以style 标签的形式追加到页面
    • 在配置文件中配置load
    module.exports = {
        module: {
            rules: [
                {
                    test: /.css$/,
                    use: ['style-loader','css-loader'] // 由后往前调用
                }
            ]
        }
    }
    
  • Loader 是 Webpack的核心特性,通过Loader 就可以加载任意类型的资源
  1. Webpack 导入资源模块
  • 打包入口 》运行入口
  • 根据代码的需要动态导入资源
    • 需要资源的不是应用,而是代码
    • 建立代码和资源的依赖关系
    • JavaScript 驱动整个前端应用
    • 逻辑合理,JS确实需要这些资源文件
    • 确保上线资源不丢失,都是必要的
  1. Webpack 文件资源加载器
  • yarn add file-loader --dev
  • output: {publicPath: ‘dist/’} 配置资源默认路径
  • 图片加载器先是把图片拷贝到输出目录,如何把图片路径当成是这个图片模块的返回值返回,这样对于应用来说,所需要的资源就发布成功了,可以通过模块的导出成员拿到图片路径
  1. Webpack URL 加载器
  • Data URLs 于 url-loader
    • Data URL可以用来表示一个文件
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RYMl48l-1626104766419)(…/img/1625267306520.jpg)]
    • 打包静态资源也可以通过这种方式去实现
    • 例如可以把png图片转换成Data URL 的形式
    • 这种形式适合体积比较小的资源
    • 小文件使用Date URLs,减少请求次数
    • 大文件单独提取存放,提高加载速度
  • yarn add url-load --dev
    • 超过10kb文件单独提取存放
    • 小于10kb文件转换为 Date URLs 嵌入代码中
    • 对于超出10kb的文件,还是会自动去调用 file-load
module.exports = {
    module: {
        rules: [
            {
                test: /.png$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 10 * 1024 // 10kb
                    }
                }
            }
        ]
    }
}
  1. Webpack 常用加载器分类
  • 编译转换器
    • 把加载到的资源模块转换为JavaScript代码,
    • 如 css-loader 把css转换成以JS形式工作的模块
  • 文件操作类
    • 把加载到的资源文件拷贝到输出目录
    • file-loader 处理图片,将文件的访问路径向外导出
  • 代码检查类
    • eslint-loader
  1. Webpack 与 ES 2015
  • Webpack因为模块打包需要,所有会处理import和export
  • yarn add babel-loader @babel/core @babel/preset-env --dev
  • babel-loader 只是一个转换js的平台,基于这个平台通过不同的插件来转换代码中具体的一些特性
  • Webpack 只是打包工具
  • 加载器可以用来编译转换代码
  1. Webpack 加载资源的方式
  • 遵循 ES Modules 标准的import 声明
  • 遵循 CommonJS 标准的rquire 函数
    • 如果是用require 导出 一个 ES Modules声明的模块,需要:rquire().default
  • 遵循AMD标准的define函数和require函数
  • *样式代码中的 @import 指令和url函数
  • *HTML代码中图片标签的src属性
  • Loader 加载的非JavaScript也会触发资源加载
    • HTML 代码中图片标签的src属性
  • yarn add html-loader --dev
    • 处理js文件中引用了html资源
    • import footer from ‘footer.html’
    // <a href="1.png"></a> 处理a标签也引用了资源的情况
    module.exports = {
        module: {
            rules: [
                {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader',
                        options: {
                            attrs: ['img:src', 'a:href'] // 默认html中图片标签的src属性才会触发资源加载
                        }
                    }
                }
            ]
        }
    }
    
  1. Webpack 核心工作原理
  • 一般是以一个js文件为打包入口,根据代码中import或require的语句,解析推断出这个文件的依赖的资源模块,然后递归的去解析这些资源模块,形成一个依赖树,递归这个依赖树,找到每个节点对应的资源文件,最后根据reles配置的加载器进行转换然后将结果放入bundle.js中
  • Loader 机制是Webpack的核心
    • 如果没有Loader就没有办法实现各种各样资源的加载,那么Webpack只能算是打包合并js代码的工具
  1. Webpack 开发一个Loader
  • 开发一个markdown-loader
    • yarn add marked --dev
    • markdown-loader.js
    const marked = require('marked')
    module.exports = source => {
        let html = marked(source)
        // console.log(html)
        // return JSON.stringify(html)  可以通过转字符串直接处理,也可以通过html-loader处理
        //  use: ['html-loader', './markdown-loader.js']
        return html
    }
    
  • Loader 负责资源文件从输入到输出的转换
  • 对于同一个资源可以依次使用多个Loader
  1. Webpack 插件机制介绍
  • 增强Webpack 自动化能力
  • Load 专注实现资源模块加载
  • Plugin 解决其它自动化工作
    • 例如:在打包前清除dist目录
    • 拷贝不需要打包的静态文件至输出目录
    • 压缩输出代码
  1. Webpack 自动清除输出目录插件
  • yarn add clean-webpack-plugin --dev
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
     plugins: [
         new CleanWebpackPlugin()
     ]
}
  1. Webpack 自动生成HTML插件(上)
  • yarn add html-webpack-plugin --dev
  • 自动生成使用bundle.js的HTML
    • 解决项目中html还在开发项目中,不方便发布的问题
    • 解决html路径引用容易出问题的情况
  • 会在html中动态的把bundle.js的引用注入进去
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
     plugins: [
         // 用于生成index.html 文件
         new HtmlWebpackPlugin({
             title: 'Webpack Plugin Sample',
             meta: {
                 viewport: 'with=device-width'
             },
             template: './src/index.html' // 配置原始模版
         }),
          new HtmlWebpackPlugin({
           filename: 'about.html'
         })
     ]
}
  1. Webpack 自动生成HTML插件(中)
  • html-webpack-plugin 选项
  1. Webpack 自动生成HTML插件(下)
  • 同时输出多个页面文件
    • 多配置几个HtmlWebpackPlugin 就解决了
  1. Webpack 插件使用总结
  • 不需要模块化处理的静态资源文件 放在public文件夹下,可以用copy-webpack-plugin 处理
  • 只会在上线前使用,如果需要打包的文件比较多,比较打,每次都去执行这个文件,性能消耗就比较大
  • new CopyWebpackPlugin([]) 需要传入一个数组,需要处理的文件路径,可以是通配符
  1. Webpack 开发一个插件
  • 相比于Loader,Plugin 拥有更宽的能力范围
    • Loader 只在Webpack 加载模块的环节工作
    • Plugin 可以涉及 Webpack的每一个环节
    • Plugin 通过钩子机制实现
    • 插件必须是一个函数或者是一个包含apply 方法的对象
    • 通过在生命周期的钩子中挂载函数实现扩展
    class MyPlugin {
        apply(compiler) {
            console.log('MyPlugin启动')
            compiler.hooks.emit.tap('MyPlugin', compilation => {
                for (const name in compilation.assets) {
                    if (name.endsWith('.js')) {
                        const contents = compilation.assets[name].source()
                        const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
                        compilation.assets[name] = {
                            source:() => withoutComments,
                            size: () => withoutComments.length
                        }
                    }
                }
            })
        }
    }
    
  1. Webpack 开发体验问题
  • 以HTTP Server 运行
  • 自动编译+自动刷新
  • 提供 Sounce Map 支持 可以根据错误信息快熟定位源代码中的问题
  1. Webpack 自动编译
  • 使用watch 的模式:监听文件变化,自动重新打包
  • yarn webpack --watch 以监视模式运行
  1. Webpack 自动刷新浏览器
  • browser-sync dist --files “**/*”
    • 监控dist下文件变化就刷新浏览器
    • 操作上比较麻烦,同时使用两个工具,开两个命令行
    • 效率上降低,这个过程中,webpack会不断将文件写入磁盘,browser-sync再不断从磁盘中把它读出来。每次都需要进行两次磁盘读写操作
  1. Webpack Dev Server
  • 官方提供的开发工具,提供用于开发的HTTP Server
  • 集成 自动编译 和 自动刷新浏览器 等功能
  • yarn add webpack-dev-server --dev
  • yarn webpack-dev-server
    • 为了提高打包效率,并没有将文件写入磁盘当中,将打包结果暂时存放在内存当中,而内部的http server也是从内存中把文件读出来,发送给浏览器。减少了磁盘操作,提升了效率
      • yarn webpack-dev-server --open 直接在浏览器打开
  1. Webpack Dev Server 静态资源访问
  • Dev Server 默认只会serve打包输出文件,只要是webpack打包输出的文件,都可以正常被访问
  • 其它静态资源文件也需要serve,需要额外的告诉Webpack Dev Server
  • contentBase 额外为开发服务器指定查找资源目录
  • devServer: { contentBase: ‘./public’ }
  1. Webpack Dev Server 代理API
module.exports = {
    devServer: { 
        contentBase: './public', 
        proxy: { 
            '/api': {
                target: 'https"//api.xx.com',
                pathRewrite: {'^/api': ''},
                // 不要使用localhost:8000作为请求xx的主机名,
                // 服务器会根据主机名判断这个请求属于哪个网站,
                // 从未把请求指派当对应的网站,这个设置会以实际发生代理的这个请求为主机名
                changeOrigin: true
            } 
        }
    }
}
  1. Source Map 简介
  • 调试和报错都是基于运行代码 源代码地图
  • Compoiled 通过 Source Map 逆向解析到源代码
  • 如 jquery
    • 在 jquery-3.4.1.min.js 文件最后一行添加注释: //# sourceMappingURL-jquery-3.4.1.min.map
    • 如果浏览器解析到这行注释,在调试模式下,会自动去请求这个Source Map 文件,根据文件内容,逆向解析源代码,便于调试
  • Source Map 解决了源代码与运行代码不一致所产生的问题
  1. Webpack 配置Source Map
  • 配置webpack.config.js devtool: ‘source-map’
  • Webpack 支持12种不同方式的Source Map
  • 每种方式的效率和效果各不相同
    • 效果最好的往往生成速度最慢
    • 而速度最快的一般生成的Source Map往往效果不好
  1. Webpack eval 模式的 Source Map
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hDHcc046-1626104766420)(…/img/1625296027733.jpg)]
  • eval() 可以直接运行js代码,在一个临时的虚拟机中
    • eval(‘console.log(123) //# sourceURL=./foo/bar.js’) 这段代码运行的环境就是./foo/bar.js
    • 可以通过sourceURL改变这段代码运行环境的名称,还是运行在临时的虚拟机中,只是告诉了这段代码的临时文件路径
  • devtool: ‘eval’ 会将每个模块的代码放在eval中执行,并在最后用sourceURL注释上路径,浏览器执行的时候就知道这段代码对应的原文件,只能定位错误出现的文件,不会生成source map ,和source map没有什么关系,构建速度最快
  1. Webpack devtool 模式对比(上)
  • module.exports = [] 可以一次生成多个打包配置
  1. Webpack devtool 模式对比(下)
  • eval- 是否使用eval 执行模块代码
  • cheap-source Map 是否包含行处理信息
  • module- 是否能够得到Loader处理之前的代码
const HtmlWebpackPlugin = require('html-webpack-plugin')

const allModes = [
    'eval',
    'eval-cheap-source-map',
    'eval-cheap-module-source-map'
]
module.exports = allModes.map(item => {
    return {
        devtool: item,
        mode: 'none',
        entry: './src/main.js',
        output: {
            filename: `js/${item}.js`
        },
        module: {
            rules: [{
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }]
        },
        plugins: [new HtmlWebpackPlugin({
            filename: `${item}.html`
        })]
    }
})
  1. Webpack 选择Source Map 模式
  • 开发模式
    • cheap-module-eval-source-map
    • 编码风格每行代码不超过80个字符,定位到行就够了
    • 一般使用vue和react经过Loader转换过后的差异较大
    • 首次打包速度慢无所谓,重写打包相对较快
  • 生产环境
    • none
    • 因为Source Map会暴露源代码
    • 调试是开发阶段的事情
    • 如果实在有问题,可以选择 nosources-source-map 不会暴露源代码内容
  1. Webpack 自动刷新的问题
  • 自动刷新功能有点积累,如果页面有填写输入框,每次修改刷新后输入框的值都消失了
  • 自动刷新导致页面状态丢失
  • 更好的办法是:在页面不刷新的前提下,模块也可以及时更新
  1. Webpack HMR 体验
  • 模块热替换 Hot Module Replacement
  • 应用运行过程中实时替换某个模块,而运行状态不改变
  • 热替换只将修改的模块实时替换至应用中
  • HMR 是webpack 中最强大的功能之一
  • 极大程度的提高了开发者的工作效率
  1. Webpack 开启 HMR
  • 集成在webpack-dev-server中
  • webpack-dev-server --hot 开启,也可以在配置文件中开启
  • 样式文件可以热更新,但是js文件不行
const webpack = require('webpack')
module.exports = {
    devServer: {
        hot: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
}
// yarn webpack-dev-server --open
  1. webpack HMR 的疑问
  • Webpack 中的HMR并不可以开箱即用
  • Webpack 中的 HMR 需要手动处理模块热替换逻辑
  • 为什么样式文件的热更新开箱即用?
    • 样式文件在style-loader 里处理了热更新的情况
  • 凭什么样式可以自动处理
    • 因为样式文件的更新比较简单,只要把更新过后的css替换到页面,就能覆盖前面的样式,从而实现样式更新
    • 而JavaScript代码是没有规律的,可能导出的是一个字符串,也可能是个函数,导出的成员使用也是各不相同的,webpack面对这些毫无规律的js模块,就不知道如何处理更新过后的模块,没有办法实现一个通用所有情况的模块替换方案
  • 我的项目没有手动处理,JS照样可以热替换
    • 框架下的开发,每个文件都是有规律的,例如react要求每个模块导出的是一个函数或者一个类
    • 通过脚手架创建的项目内部都集成了HMR方案,所以不需要手动处理
  • 总结:我们需要手动处理JS模块更新后的热替换
  1. Webpack 使用 HMR API
  • 如果js模块的更新被手动处理,就不回去刷新页面了
import a from './a'
const b = a()

module.hot.accept('./a', () => {
    console.log('a 模块更新了,需要这里处理一下热更新替换逻辑') 
    // 修改模块a不会导致页面刷新,而是会触发这里
})
  1. Webpack 处理JS 模块热替换
  • 不是一个通用的方式,只是针对当前的模块
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJ0xozSG-1626104766421)(…/img/1625302722081.jpg)]
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sf52OEek-1626104766422)(…/img/1625302787509.jpg)]
  1. Webpack 处理图片模块热替换
import bgImg from './b.png'
const img = new Image()
img.src = bgImg
if (module.hot) {
    module.hot.accept('./b.png', () => {
      img.src = bgImg
    })
}
  1. WebPack HMR 注意事项
  • 处理HMR 的代码报错会导致自动刷新,这样就不好排查错误
    • devServer: { hotOnly: ture }, 是否被处理了热替换,浏览器都不会去刷新
  • 没启动HMR的情况下,HMR API 报错
    • if (module.hot) 先判断,再使用
    • 如果关闭配置文件中的热更新,那么代码中多了一些与业务无关的代码(自己写的热更新API代码),这个会在编译后被移除,只剩下 if(false){},这个没有意义的判断在代码压缩后也会去掉,不会影响生产环境
  1. webpack 生产环境优化
  • 生产环境注重运行效率
  • 开发环境注重开发效率
  • 为不同的工作环境创建不同的配置
  1. webpack 不同环境的配置文件
  • 配置文件根据环境不同导出不同配置
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ic6oSi5-1626104766423)(…/img/1625307578973.jpg)]
    • yarn webpack --env production
    • 只适合中小型项目,如果项目大了,配置也会复杂起来
  • 一个环境对于一个配置文件
  1. Webpack 不同环境的配置文件
  • 大型项目,不同环境对应不同配置文件
  • yarn add webpack-merge --dev 合并对象的包
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0xsp7Aue-1626104766424)(…/img/1625308031835.jpg)]
  • yarn webpack --config webpack.prod.js 或者 package.json > “scripts”: {“build”: “webpack --config webpack.prod.js” }
  1. webpack DefinePlugin
  • 为代码注入全局成员
  • 代码启动起来,会注入一个 prosess.env.NODE_ENV
  • 其它模块根据这个判断全局的执行环境,判断是否打印日志等操作
const webpack = require('webpack')
module.exports = {
    plugins: [
        new webpack.DefinePlugin({ // 参数是一个对象,表示需要注入的全局变量,注入的是一个代码片段,而不是一个字符串
           API_BASE_URL: '"https://api.xx.com"'
        })
    ]
}
  1. Webpack 体验 Tree Shaking
  • 摇掉 代码中未引用部分 生产模式下自动开启
  1. webpack 使用 Tree Shaking
  • Tree Shaking 不是指某个配置选项,而是一组功能搭配使用后的优化效果
  • prodution 模式下自动启用
  • 在其它环境配置使用 Tree Shaking
module.exports = {
    optimization: { // 专门配置优化选项的地方
       usedExports: ture, //  负责标记 枯树叶
       minimize: ture // 负责摇掉 它们
    }
}
  1. webpack 合并模块
module.exports = {
    optimization: { // 专门配置优化选项的地方
       usedExports: ture, //  负责标记 枯树叶
       concatenateModules: true, // 尽可能的将所有模块合并输出到一个函数中,既提升了运行效率,又减小了代码的体积
       //  Scope Hoisting 也就是作用域提升 webpack 3中添加的特性
       // minimize: ture // 负责摇掉 它们
    }
}
  1. webpack Tree Shaking 与 Babel
  • 很多资料表示,使用Babel会导致Tree Shaking失效
  • Tree Shaking 前提是 ES Module,由Webpack打包的代码必须使用ESM实现的模块化
  • Babel 会把 ESM 转换成 CommonJS 模块化的规范
  • 在最新版本的 Babel-loader 中自动帮我们关闭了ESM 转换的插件,标记了当前环境支持ESM
module.exports = {
   module: {
       rules: [
           {
               test: /\.jd$/,
               use: {
                   loader: 'babel-loader',
                   options: {
                       presets: [
                           ['@babel/preset-env', {modules: 'commonjs'}] // 配置为commonjs,那么Tree Shaking又不生效了
                           // modules: false 确保 preset-env 中不会开启 ESM转换的插件
                       ]
                   }
               }
           }
       ]
   }
}
  1. webpack sideEffects
  • webpack4新特性,可以通过配置的方式允许代码中是否有副作用
    • 副作用:模块执行时除了导出成员之外所做的事情
    • sideEffects 一般用于npm包标记是否有副作用
  • 要配置两个地方,
    • 如 components 下有多个模块,全部在 index.js 中导出,然后main.js 引用了index.js,导出了其中一个成员,有副作用会全量打包
    • package.json > “sideEffects”: false 标识这个package所影响的项目都没有副作用,没有用到的模块,没有副作用后,都会被移除
    • webpack.config.js > optimization: { sideEffects: true } 表示开启这个功能
  1. webpack sideEffects 注意
  • 确保代码真的没有副作用,否则在webpack打包时就会误删掉这些有副作用的代码
    • 例如一个模块中给 Number的原型加一个方法,但没有导出。在 index.js引用,就可以使用Number原型的方法,并不用导出
    • 如果标识代码没有富作用,这个扩展操作就不会被打包
    • 还有在代码中载入的css模块
    • 解决方法:可以在package.json 中标记有副作用的模块; package.json > “sideEffects”: ["./src/extend.js", “*.css”]
  1. Webpack 代码分割
  • Code Splitting 代码分包、代码分割
  • 所有代码最终都被打包到一起
  • 如果应用过于复杂,代码过多,bundle体积过大
  • 然而在应用开始工作时,并不是每个模块在启动时都是必要的
  • 模块打包是有必要的
    • 同域并行请求限制。每次请求都会有一定的延迟
    • 请求过多的Header 浪费带宽流量
  • 有两种解决方案
    • 多入口打包
    • 动态导入 实现ESM 的动态加载的功能
  1. webpack 多入口打包
  • 适用多页应用程序
    • 一个页面对应一个打包入口
    • 公共部分单独提取
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23QQO1Fy-1626104766424)(…/img/1625322017033.jpg)]
  1. webpack 提取公共模块
  • 不同入口肯定会有公共模块
  • webpack.config.js > optimization: { splitChunks:{ chunks: “all” } }
    • 表示把所有公共模块都提取到一个文件(bundle)中
  1. Webpack 动态导入
  • 按需加载:需要用到某个模块时,再加载这个模块
  • 所有被动态导入的模块会被自动分包,提取到单独的bundle中
  • 比起多入口更加灵活,可以通过代码的方式决定需不需要加载某个模块
  • 在需要动态导入的地方使用ESM的 import().then()函数导入
    • webpack 会自动处理分包和按需加载
    • 如果使用的是单页应用开发框架,在项目中的路由映射组件就可以通过这种动态导入的方式按需加载
  1. webpack 魔法注释
  • import(/* webpackChunkName: ‘album’ */ ‘./album/album’).then()
    • 可以通过这种注释的方式给动态导入的模块打包后的bundle进行命名
    • 相同的chunkName会被打包在一起
  1. Webpack MiniCssExtractPlugin
  • 提取css到单个文件,可以实现css的按需加载
  • yarn add mini-css-extract-plugin --dev
  • 如果样式文件的体积不是很大,那提取到单个文件中效果可能适得其反
  • 如果css 超过了150kb左右,才需要考虑是否要提取到单独的文件中
const MiniCssExtractPlugin = require('MiniCssExtractPlugin')
module.exports = {
    moudule: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    // 'style-loader' // 将样式通过style 标签注入页面
                    MiniCssExtractPlugin.loader, // 痛殴 style 标签的 link 属性引用css文件
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin()
    ]
}
  1. webpack OptimizeCssAssetsWebpackPlugin
  • 压缩输出的css文件
  • yarn add optimize-css-assets-webpack-plugin --dev
    • 这个插件官方推荐配置在 minimizer中的原因是,只在需要压缩的时候才压缩,如果配置在plugins中,调试模式css文件也会被压缩
    • 以普通模式打包,不开始压缩,也就不会调用
  • yarn add terser-webpack-plugin --dev
    • webpack自带的js压缩插件,如果配置了 minimizer 属性,原来的被覆盖,要重写
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2V6dr4l-1626104766425)(…/img/1625324133106.jpg)]
  1. webpack 输出文件名 Hash
  • 一般会开启客户端静态资源缓存提升性能
    • 缓存时间设置过短,效果不明显
    • 如果设置的时间比较长,应用发生更新,重新部署过后,又没有办法及时更新到客户端
    • 为了解决这个问题,推荐在生产模式下,文件名使用Hash
  • 有三种hash
    • hash 项目级别的hash,整个项目的文件hash一样,修改一个文件,重新打包,所有文件hash都变了
    • chunkhash chunk级别hash 在打包过程中,同一入口的打包,模块hash都是相同的
      -(动态导入也算多个chunk,如果其中一个变了,引用这个动态导入的文件也会被动改变,因为路径更新)
    • contenthash 文件级别的hash 根据输出文件的内容输出的hash值,不同文件不同hash值,也会路径更新
      • 精确的定位到了文件级别的hash,最适合解决缓存问题
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8qgERFy-1626104766425)(…/img/1625325323722.jpg)]
任务三:其它打包工具
  1. Rollup 概述
  • Rollup 更为小巧,仅仅是一款ESM 打包器
  • Rolluo 中并不支持类似HMR这种高级特性
  • Rolluo并不是要与Webpack 全面竞争,只是为了提供一个充分利用ESM各项特性的高效打包器
  1. Rollup 快速上手
  • yarn add rollup -dev
  • yarn rollup ./src/index.js --format iife --file ./dist/bundle.js 参数为打包入口和打包模式(格式)以及打包输出目录
  • rollup 打包后代码很简洁,基本等于自己手写的转换的iife代码,没有引用的代码会被去掉
  • Tree Shaking 摇树最早出现在rollup,默认开启
  1. Rollup 配置文件
  • rullup.config.js
  • yarn rollup --config rullup.config.js
import json from 'rollup-plugin-json'
import resolve from 'rollup-plugin-node-resolve'
export default {
    input: 'src/index.js',
    output: {
        file:'dist/bundle.js',
        format: 'iife'
    },
    plugins: [
        json(), // 将函数的结果放入
        resolve()
    ]
}
  1. Rollup 使用插件
  • 插件是Rollup 扩展的唯一方式
  • yarn add rollup-plugin-json --dev 解析json 数据,可以直接在项目中 import {} from ‘./a.json’ 那数据
  1. Rollup 加载 NPM 模块
  • yarn add rollup-plugin-node --dev 只有通过这种方式, Rollup才能通过模块名称导入第三方模块
  • Rollup 默认只能处理ESM模块
  1. Rollup 加载CommonJS 模块
  • yarn add rollup-plugin-commonjs --dev 这个插件解决Rollup不能使用CommonJS模块的问题
  1. Rollup 代码拆分
  • 使用动态导入实现 import(’./logger’).then({ log } => { log(’’) })
  • yarn rollup --config amd 不支持iife方式,因为Rollup打包过后比价简单,没有像webpack 一样的模块机制,只能使用amd标准
export default {
    input: 'src/index.js', // ['src/a.js', 'src/b.js'] {a: 'src/a.js', b: 'src/b.js'}
    output: {
        // file:'dist/bundle.js',
        // format: 'iife'
        dir: 'dist', // 多文件都打包到dist目录下
        format: 'amd' // 多文件打包和多入口打包都不能使用IIFE模式
    }
}
  1. Rollup 多入口打包
  • 支持多入口打包,公共文件也会自动提取到单独的bundle
  • 多入口在 input 入口配置为对象或者数组即可
  • amd 打包的文件,不能直接在html页面调用,要使用管理amd模块的库去调用
    • require.js
  1. Rollup 选用原则
  • 优点
    • 输出的结果更加扁平,执行效率更高
    • 自动移除为引用代码
    • 打包结果依然完全可读,和手写的差不多
  • 缺点
    • 加载非ESM 的第三方模块比较复杂
    • 模块最终都被打包到一个函数中,无法实现HMR
    • 浏览器环境中,代码拆分功能依赖AMD库
  • 开发应用
    • 大量引用第三方模块
    • 需要HMR 这样的功能提升开发体验
    • 应用大了要分包
    • rollup 在满足这些上有欠缺
  • 开发一个框架或者类库
    • 优点很明显,缺点不太重要了,开发框架或库,很少依赖第三方模块
    • 大多数知名框架/库都在使用Rollup作为模块打包器,如vue 和react
  • webpack 大而全,Rollup小而美
    • 应用开发使用webpack
    • 库/框架开发使用Rollup
    • 随着webpack这几年的发展,rollup的很多优势被慢慢磨平了,如扁平化输出,webpack可以通过插件实现
  1. Parcel 零配置的前端应用打包器
  • yarn add parcel-bundler --dev
  • parcel 和webpack 一样,可以用任意文件做打包入口,但是官方推荐html,理由是html是应用运行在浏览器中的入口
  • yarn parcel src/index.html
  • 支持热更新 if(module.hot) { module.hot.accept(() => {}) } 与webpack不一样,只支持一个参数,如果当前模块,或者依赖模块修改触发
  • 支持自动安装依赖,不用重启调试,代码保存过后自动下载依赖
  • 同样支持加载其它类型的资源模块,加载任意模块的资源,都是0配置
  • 支持动态导入,自动拆分代码
  • yarn parcel build src/index.html 生产模式打包
  • 相同体积的项目打包,percel会比webpack快很多,percel内部使用多进程同时工作,充分发挥多核cpu性能,webpack 可以通过happypack的插件实现这一点
  • 2017年发布,出现的原因就是当时wbpack 使用上过于繁琐,官方文档也不是很清晰明了
  • 完全零配置,对项目没有任何侵入
  • 没有普及的原因:webpack 有更好的生态,随着这两年发展,webpack越来越好用
任务四:规范化标准
  1. 规范化介绍
  • 为什么要有规范化标准
    • 软件开发需要多人协同
    • 不同开发者具有不同的编码习惯和喜好
    • 不同的喜好增加项目维护成本
    • 每个项目或者团队需要明确统一的标准
  • 那些需要规范化标准
    • 代码、文档、甚至是提交日志
    • 开发过程中人为编写的产出
    • 代码标准化规范最为重要
  • 实施规范化的方法
    • 编码前人为的标准约定
    • 通过工具实现Lint
  • 常见的规范化实现方式
    • ESLint 工具使用
    • 定制ESLint校验规则
    • ESLint对TypeScript的支持
    • ESLint 结合自动化工具或者webpack
    • 基于ESLint的衍生工具
    • Stylelint 工具的使用
  1. ESLint 介绍
  • 最为主流的JavaScript Lint工具 监测JS代码质量
  • ESLint 很容易统一开发者的编码风格
  1. ESLint 安装
  • 安装ESLint模块为开发依赖
  • 通过CLI命令验证安装结果
  • yarn add eslint --dev 或 npm install eslint --save-dev
  1. ESLint 快速上手
  • yarn eslint --init
    • 第一个选项:出现以下三种检查选项
    • 语法错误
    • 语法错误+问题代码(如变量声明没有使用)
    • 语法错误+问题代码+编码风格
  • 代码存在问题,eslint是没有办法去找到问题代码和编码风格问题的
  • yarn eslint 文件名 --fix 自动修复编码风格绝大多数问题
  1. ESLint 配置文件解析
  • 支持同时配置多个环境,可以同时配置browser和node,那么这两个环境的全局成员就都可以使用
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bw7Vvmmh-1626104766426)(…/img/1625384928864.jpg)]
  • parserOptions 配置监测语法是否可用
  • rules 配置规则是否可用
  • globals 配置支持的全局变量,比如jquery
  1. 配置注释
  • 将配置的通过注释的方式写在脚本文件中
  • 直接在某行代码后 + // eslint-disable-line 具体规则 如果不加具体规则,就所有的规则都不检测
  1. ESLint 结合自动化工具(Gulp)
  • 集成之后,确保ESLint每次都会工作
  • 与项目统一,管理更加方便
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VGX2wTg6-1626104766427)(…/img/1625385679827.jpg)]
  • mac command + k + 0 快速折叠代码
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UIXwpRDl-1626104766427)(…/img/1625385986619.jpg)]
  1. ESLink 结合 Webpack
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CrPr5jJY-1626104766428)(…/img/1625386403254.jpg)]
  • eslint 在webpack中的两种配置方式
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9MXGrvo-1626104766428)(…/img/1625386698044.jpg)]
  1. ESLint 结合 Webpack 后续配置
  • yarn add eslint-plugin-react --dev 针对react编写的
  • 两种方式
    • 在.eslintrc.js 中配置: plugins: [‘react’] rules: [‘react/jsx-uses-react’: 2, ‘react/jsx-uses-vars’: 2]
    • .eslintrc.js 》 extends: [‘plugin:react/recommended’]
  1. 现代化项目集成ESLint

  2. ESLint 检查 TypeScript

  3. Stylelint认识

  • 提供默认的代码监测规则
  • 提供CLI工具,快速调用
  • 通过插件支持Sass Less PostCSS
  • 支持Gulp或Webpack集成
  • 使用
    • yarn add stylelint --dev
    • .stylelintrc.js
    • yarn add stylelint-config-standard
    module.exports = {
        extends: ["stylelint-config-standard", "stylelint-config-sass-guidelines"]
    }
    
    • yarn add stylelint-config-sass-guidelines --dev 检查sass代码的插件
  1. Prettier 的使用
  • 前端代码格式化工具
  • yarn add prettier --dev
  • yarn prettier 文件名 --write 直接将格式化的代码输出到命令行,加上–write才会覆盖原文件
  • yarn prettier . --write 格式化所有代码
  1. Git Hooks 介绍
  • 代码提交至仓库之前未执行lint工作
  • Git Hook 也称之为git钩子,每个钩子都对应一个任务
  • 通过shell脚本可以编写钩子任务触发时要具体执行的操作
  • .git > pre-commit.sample git commit 提交本地仓库前执行的钩子
  1. ESLint 结合 Git Hooks
  • 很多前端开发者并不擅长使用shell
  • Husky 可以实现Git Hooks 的使用需求,在不编写shell脚本情况下,也能使用git钩子
  • yarn add husky --dev
    • package.json > “husky”: { “hooks”: { “pre-commit”: “npm run precommit” } }
    • 如果接下来想自动格式化,然后把代码推入暂存区就无能为力了
  • yarn add lint-staged --dev
    • package.json > “lint-staged”:{ “*.js”: [“eslint”, “git add”] } “scripts”: { “precommit”: “lint-staged” }
  • Husky + lint-staged 配合使用
任务五:webpack 源码
  1. 内容概述
  • 将webpack打包任务拆解成不同的环节,掌握每个环节所做的工作
  • 实现一个自己的打包器,不同模块的加载,以及不同loader和插件的使用
  1. 打包后文件分析
  • webpack 4 打包后文件解析,
  • webpack5 打包后,模块不再是传入,而是直接在自执行函数中声明一个对象储存
    • 如果入口模块没有导出,直接就在最下面调用执行了,不会储存在开始的模块对象中
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0u33GDzr-1626104766429)(…/img/1625466911614.jpg)]
  • webpack_require 方法,它的核心作用是返回模块的exports
  1. 单文件打包后源码调试

  2. 函数功能说明(CommonJS模块打包)

  3. CommonJS模块打包(CommonJS模块打包 和 ESM 模块打包)

  • 对比 CommonJS模块 和ESM 模块 webpack打包后文件的区别
  • webpack 默认就支持CommonJS,没有对模块做任何处理
  1. esModule 模块打包
  • 主入口是 ESM,调用了commonjs模块和ESM打包后输出
// webpack 5 打包输出
(() => { // webpackBootstrap
    var __webpack_modules__ = ({
    
    /***/ "./src/login.js": // ESM 模块
    /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { // module, module.exports, __webpack_require__
          "use strict";
          __webpack_require__.r(__webpack_exports__);  // 先给exports 标记这是一个 _esModule
           __webpack_require__.d(__webpack_exports__, {  // 把导出数据挂载到exports 上
             "default": () => (__WEBPACK_DEFAULT_EXPORT__),
             "age": () => (/* binding */ age)
           });
          /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ('登陆');
          console.log(22)
          const age = 22
         }),
    
    /***/ "./src/msg.js": ((module) => { module.exports = { msg: '今天红烧肉'} }) // commonjs模块,基本不做处理
    
    });
/************************************************************************/
 	// The module cache
 	var __webpack_module_cache__ = {};
 	
 	// The require function
 	function __webpack_require__(moduleId) {  // 通过模块ID (一般是文件名)返回模块导出结果 (exports)
 		// Check if module is in cache
 		var cachedModule = __webpack_module_cache__[moduleId];
 		if (cachedModule !== undefined) {
 			return cachedModule.exports;
 		}
 		// Create a new module (and put it into the cache)
 		var module = __webpack_module_cache__[moduleId] = {
 			// no module.id needed
 			// no module.loaded needed
 			exports: {}
 		};
 		// Execute the module function 通过模块ID调用模块
 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
 		// Return the exports of the module
 		return module.exports;
 	}
 	
/************************************************************************/
 	/* webpack/runtime/compat get default export */
 	(() => {
 		// getDefaultExport function for compatibility with non-harmony modules  用于与非和谐模块兼容的 getDefaultExport 函数
     // 把ESM 默认值处理往comoonjs方向上统一
 		__webpack_require__.n = (module) => { // module 》esm 的导出对象 
 			var getter = module && module.__esModule ?
 				() => (module['default']) :
 				() => (module);
 			__webpack_require__.d(getter, { a: getter });
 			return getter;
 		};
 	})();
 	
 	(() => {
 		// 给模块导出数据挂载到exports,通过defineProperty get使导出结果修改无效
 		__webpack_require__.d = (exports, definition) => {
 			for(var key in definition) {
 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
 				}
 			}
 		};
 	})();
 	
 	/* 判断对象是否有该属性 */
 	(() => {
 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
 	})();
 	(() => {
 		// define __esModule on exports 给 exports加上 __esModule标记
 		__webpack_require__.r = (exports) => {
 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
 			}
 			Object.defineProperty(exports, '__esModule', { value: true });
 		};
 	})();
  var __webpack_exports__ = {}; // This entry need to be wrapped in an IIFE because it need to be in strict mode.
  (() => {
  "use strict";
  /*!**********************!*\
    !*** ./src/index.js ***!
    \**********************/
  __webpack_require__.r(__webpack_exports__); // 打标记
   __webpack_require__.d(__webpack_exports__, { // 把导出数据挂载到exports 上
     "default": () => (__WEBPACK_DEFAULT_EXPORT__)
   });
   var _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login.js */ "./src/login.js"); // 调用模块拿到导出数据
   var _msg_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./msg.js */ "./src/msg.js");
   var _msg_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_msg_js__WEBPACK_IMPORTED_MODULE_1__); // 特殊处理ESM 模块的默认值
  console.log('index.js 内容', _login_js__WEBPACK_IMPORTED_MODULE_0__.default, _msg_js__WEBPACK_IMPORTED_MODULE_1__.msg)
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ('入口文件导出内容');
  })();
})();
  1. 功能函数手写实现01

  2. 功能函数手写实现02

  3. 懒加载实现流程梳理

  • import() 可以实现指定模块的懒加载操作
  • 当前懒加载核心原理就是 jsonp
  • t 方法可以针对于内容进行不同的处理 (处理方式取决于传入的数值 8 6 7 3 2 1)
  1. t 方法分析及实现

  2. 单文件懒加载源码分析1

  3. 单文件懒加载源码分析2

  4. 单文件懒加载手写实现

  5. webpack 与 tapable

  • 专注于自定义事件的触发和处理
  • webpack 编译流程
    • 配置初始化
    • 内容编译
    • 输出编译后内容
  • 事件驱动型事件流工作机制
    • 负责编译的complier
    • 负责创建bundles的compilation
  • tapable 本身是一个独立的库
    • 实例化hook注册事件监听
    • 通过hook触发事件监听
    • 执行懒编译生成的可执行代码
    • hook 本质是tapable实例对象
    • hook 执行机制可分为同步和异步(异步存在并行和串行两种机制)
  • hook执行特点
    • hook:普通钩子,监听器之间互相独立不干挠
    • BailHook:熔断钩子,某个监听返回非undefined 时后续不执行
    • WaterfallHook:瀑布钩子,上一个监听返回值可传递至下一个
    • LoopHook:循环钩子,如果当前未返回false 则一直执行
  • tapable 库同步钩子
    • SynckHook
    • SyncBailHook
    • SyncWaterfallHook
    • SyncLoopHook
  • tapable 库异步串行钩子
    • AsyncSeriesHook
    • AsyncSeriesHailHook
    • AsyncSeriesWaterfallHook
  • tapable 库异步并行钩子
    • AsyncParelleHook
    • AsyncParelleBailHook
  1. 同步钩子使用及调试

  2. 异步钩子的使用

  3. SyncHook 源码调试1

  4. syncHook 源码调试2

  5. 手写 SyncHook1

  6. 手写 SyncHook2

  7. AsyncParallelHook源码分析

  8. AsyncParallelHook实现

  9. 定位webpack 打包入口

  • 01 node_modules/.bin/webpack.cmd cmd 文件核心的作用就组装了 node *****/webpack/bin/webpack.js
  • 02 webpack.js 中核心的操作就是require了 node_modules/webpack-cli/bin/cli.js
  • 03 cli.j
    • 当前文件一般有两个操作,处理参数,将参数交给不同的逻辑(分发业务)
    • options
    • complier
    • complier.run
  1. 编译主流程调试
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWtlNHCq-1626104766430)(…/img/1625839062839.jpg)]
  1. 手写webpack实现
  • lgpack
    • lib
      • webpack.js
    • npm init -y
      • “main”: “./lib/webpack.js”
  • webpack.js
    1. 实例化compiler 对象
    2. 初始化 NodeEnvironmentPlugin(让compiler具备文件读写能力)
    3. 挂载所有 plugins 插件 至compiler 对象上
    4. 挂载所有webpack内置的插件(入口)
    5. 返回 compiler 对象即可
  • npm i tapable@1 -D
  1. EntryOptionPlugin 执行分析
  • 入口模块
  1. EntryOptionPlugin 流程手写
  • WebpackOptionsApply > 290行
  1. run 方法分析及实现

  2. compile 分析及实现

  3. newCompilationParams 方法调用,返回params, normalModuleFactory

  4. 上述操作是为了获取 params

  5. 接着调用了beforeComlile 钩子监听,在它的回调中又触发了compile 监听

  6. 调用 newCompilation 方法,传入了上面 params,返回了一个 compilation

  7. 调用了一个 createNewCompilation (Compilation.js)

  8. 上述操作完成之后就可以触发 make 钩子监听

  9. make 前流程回顾

  10. 步骤

  11. 实例化 compiler 对象 (它会贯穿整个webpack工作流程)

  12. 由 compiler 调用 run方法

  13. compiler 实例化操作

  14. compiler 继承 tapable, 因此它具备钩子的操作能力 (监听事件,触发事件,webpack 是一个事件流)

  15. 在实例化 compiler 对象之后就往它的身上挂载很多属性,其中 NodeEnvironmentPlugin 这个操作就让它具备流文件读写的能力(我们在模拟时采用的是 node 自带的 fs)

  16. 具备了 fs 操作能力之后又将 plugins 中插件都挂载到了 compiler 对象身上

  17. new WebpackOptionsApply().process(options, compiler)

  - 将内部默认的插件与 compiler 建立关系,其中 EntryOptionPlugin(290行) 处理了入口模块 id
05. 在实例化 compiler 时候只是监听了 make 钩子 (EntryOptionPlugin > SingleEntryPlugin)
  1. 在 SingleEntryPlugin 模块的 apply 方法中有两个钩子监听
  2. 其中 compilation 钩子就是让compilation 具备了利用 NormalModuleFactory 工厂创建一个普通模块的能力
  3. 因为它就是利用一个自己创建的模块来加载需要被打包的模块
  4. 其中 make 钩子在 compiler.run 的时候会被触发,走到这里意味着某个模块执行打包之前的所有准备工作就完成了
  5. addEntry 方法调用
  1. run 方法执行 (当前想看的是什么时候出发了 make 钩子)
  2. run 方法里就是一堆钩子按着顺序触发 (beforeRun run compile)
  3. compiler 方法执行
  4. 准备参数(其中 NormalModuleFactory 是我们后续用于创建模块的)
  5. 触发 beforeCompile
  6. 将第一步的参数传递给一个函数,开发创建一个 compilation(newCompilation)
  7. 在调用 newCompilation 的内部
    - 调用了 createCompilation
    - 触发了 this.compilation 钩子和 compilation 钩子的监听
03. 当创建了 compilation 对象之后就触发了 make 钩子
04. 当我们触发 make 钩子监听的时候,将 compilation 对象传递了过去
  1. 总结

  2. 实例化 compiler

  3. 调用 compiler 方法

  4. newCompilation

  5. 实例化一个 compilation 对象 (它和 compiler 有关系)

  6. 触发 make 监听

  7. addEntry 方法 (这个时候就带着 context name entry 一堆的东西) 奔着编译去了

  8. addEntry 流程分析1

  9. make 钩子在被触发的时候,接受到了 compilation 对象实现, 它的身上挂载了很多内容

  10. 从 compilation 当中解构了三个值

- entry: 当前需要被打包的模块的相对路径
- name: main
- context: 当前项目的跟路径
  1. dep 是对当前的入口模块中的依赖关系进行处理

  2. 调用了 addEntry 方法

  3. 在 compilation 实例的身上有一个 addEntry 方法,然后内部调用了 _addmoduleChain 方法,去处理依赖

  4. 在 compilation 当中我们可以通过 NormalModuleFactory 工厂来创建一个普通模块对象

  5. 在 webpack 内部默认启动一个100 并发量的打包操作(this.semaphore.acquire),当前我们看到的是 normalModule.create()

  6. 在(moduleFactory.create中) beforeResolve 里面会触发一个 factory 钩子监听 【这个部分的操作其实是处理loader】

  7. 上述操作完成之后获取到了一个函数存在 factory 里,然后对它进行了调用

  8. 在这个函数调用里又触发了一个 resolver 的钩子(处理 loader的,拿到了 resolver 方法就意味者所有的loader处理完毕)

  9. 调用 resolver() 方法之后,就会进入到 afterResolve 这个钩子里,然后就会触发 new NormalModule

  10. addEntry 流程分析2

  11. addEntry 初始化

  12. _addModuleChain实现

  13. buildModule 实现

  14. build及parse 实现

  • yarn add babylon --dev
  1. 处理模块依赖01
  • astexplorer.net 这个网站可以看js代码的语法树
  1. 处理模块依赖02

  2. 抽离createModule方法

  3. 编译依赖模块

  • yarn add neo-async --dev
  1. chunk流程分析及实现

  2. 生成chunk代码

  • yarn add ejs --dev
  1. 生成打包文件
  • yarn add mkdirp --dev
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttBEKBlf-1626104766431)(…/img/1626064121728.jpg)]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值