简介 什么是 webpack
webpack is a module bundler (模块打包工具)
webpack 可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其他的一些浏览器不能直接运行的拓展语言(scss, TypeScript 等),并将其打包为合适的格式以供浏览器使用。
安装
检查是否安装成功
1 2 3 4 5 webpack -v # 查看 webpack 版本(全局安装才能查看) npx webpack -v # npx 帮助我们在项目中的 node_modules 里查找 webpack ./node_modules/.bin/webpack -v # 直接到当前 node_modules 模块里指定 webpack
测试:启动 webpack 打包 1 2 3 4 5 import add from './a' ; import minux from './b' ;
1 npx webpack # 打包命令,使用webpack处理index.js这个文件
总结:webpack是一个模块打包工具 ,可以识别出引入模块的语法,早期的 webpack 只是个 js 模块的打包工具,现在可以使 css,png,vue 的模块打包工具
webpack 配置文件 零配置是很弱的,特定的需求,总是需要自己进行配置
1 2 3 4 module .exports = { entry: './src/index.js' , output: './dist/main.js' }
当我们使用 npx webpack
,表示的是使用 webpack 处理打包,./src/index.js
为入口模块。默认出口放在当前目录下的 dist 目录,打包后的模块名称是 main.js,当然我们可以自行修改
webpack 有默认的配置文件,webpack.config.js ,我们可以对这个文件进行修改,进行个性化配置
默认配置文件:wbepack.config.js
1 npx webpack # 执行命令后,webpack 会找到默认的配置文件,并使用执行
不使用默认的配置文件:webpackconfig.js
1 npx webpack --config webpackconfg.js # 指定webpack使用webpackconfig.js文件作为配置文件
修改 package.json scripts 字段,使用 npm run xxx
来启动
原理:模块局部安装会在 node_modules/.bin 目录下创建一个软链接
1 2 3 "scripts": { "dev": "webapck" }
webpack.config.js 配置结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module .exports = { entry: './src/index.js' , output: './dist' , mode: 'production' , module : { rules: [ { test: /.css$/ , use: 'style-loader' } ] }, plugins: [ new HtmlWebpackPlugin() ] }
webpack 核心概念 entry ? 指定 webpack 打包入口文件,webpack执行构建的第一步将从 entry 开始,可抽象成输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module .exports = { entry: { main: './src/index.js' }, entry: './src/index.js' , entry: { index: './src/index.js' , login: './src/login.js' } }
output ? 打包转换后的文件输出到磁盘位置以输出结果,在webpack经过一系列处理并得出最终代码后输出结果
1 2 3 4 5 6 7 8 9 10 11 12 module .exports = { output: { filename: 'bundle.js' , path: path.resolve(__dirname, 'dist' ) } output: { filename: '[name][chunkhash:8].js' , path: path.resolve(__dirname, 'dist' ) } }
mode ? mode 用来指定当前的构建环境
production
development
none
选项
描述
development
设置 process.env.NODE_ENV
的值为 development
,开启 NamedChunksPlugin
和 NamedModulesPlugin
production
设置 process.env.NODE_ENV
的值为 production
,开启 FlagDependencyUsagePlugin
,FlagIncludedChunksPlugin
,ModuleConcatenationPlugin
,NoEmitOnErrorsPlugin
,OccurrenceOrderPlugin
,SideEffectsFlagPlugin
和 TerserPlugin
node
不开启任何优化选项
如果没有设置,webpack 会将 mode
的默认值设置为 production
。
记住,设置 NODE_ENV
并不会自动的设置 mode
开发阶段的开启会有利于热更新的处理,识别哪个模块变化
生产阶段的开启会有帮助模块压缩,处理副作用等一些功能
loader ? 模块解析,模块转换器,用于把模块原内容按照需求转换成新内容
? webpack 是模块打包工具,儿模块不仅仅是 js,还可以是 css,图片或者其他格式
? 但是 webpack 默认只知道处理 js 和 json 模块 ,那么其他格式的模块处理和处理方式就需要 loader
常见的 loader
1 2 3 4 5 6 7 8 9 style-loader css-loader less-loader sass-loader ts-loader babel-loader file-loader eslint-loader ...
module 模块,在 webpack 里一切皆模块,一个模块对应一个文件,webpack 会从配置的 entry 开始递归找出所有依赖的模块
1 2 3 4 5 6 7 8 9 10 module : { rules: [ { test: /.xxx$/ , use: { loader: 'xxx-loader' } } ] }
当 webpack 处理到不认识的模块时,需要再 webpack 中的 module 里进行配置,当检测到是什么格式的模块,使用什么 loader 来处理
file-loader 处理静态资源模块
原理是把打包入口中识别出的资源模块,移动到输出目录,并且返回一个地址名称
使用场景:
? 当我们需要模块仅仅是从源代码挪移到打包目录,就可以使用 file-loader 来处理,txt, svg, csv, excel, 图片等资源
1 npm install file-loader -D
处理图片
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module : { rules: [ { test: /.(png|jpe?g|gif)$/ , use: { loader: 'file-loader' , options: { name: '[name]_[hash].[ext]' , outputPath: 'images/' , publicPath: '../images' } } } ] }
1 2 3 4 5 6 7 8 import logo from './webpack.jpg' let img = new Image();img.src = logo; img.classList.add('logo' ); let root = document .getElementById('root' );root.append(img)
处理字体
1 2 3 4 5 6 7 8 9 10 11 12 // css @font-face { font-family : "webfont" ; font-display : swap; src : url ("webfont.woff2" ) format ("woff2" ); } body { font-size : 24px ; font-family : "webfont" ; background-color : #f40 ; }
1 2 3 4 5 { test: /.(eot|ttf|woff|woff2|svg)$/ , use: 'file-loader' }
url-loader url-loader 内部使用了 file-loader,所以可以处理 file-loader 所有的事情,但是遇到 体积较小图片模块会把该图片转换为 base64 格式字符串,并打包到 js 里。对小体积的图片比较适合,大图片得不偿失。
1 npm install url-loader -D
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module : { rules: [ { test: /.(png|jpe?g|gif)$/ , use: { loader: 'url-loader' , options: { name: '[name].[ext]' , outputPath: 'images/' , limit: 2048 } } } ] }
样式处理 css-loader 分析模块之间的关系,并合成一个css
style-loader 会把 css-loader 生成的内容,以style标签包裹并挂载到页面的head部分
1 npm install style-loader css-loader -D
1 2 3 4 5 { test: /.css$/ , use: ['style-loader' , 'css-loader' ] }
Plugins plugin 可以在 webpack运行到某个阶段的时候,帮你做一些事情,类似于生命周期的概念
扩展插件,在 webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情,作用于整个构建过程
HtmlWebpackPlugin ? html-webpack-plugin 会在打包结束后,自动生成一个 html 文件,并把打包的生成的 js 模块注入到该 html 中
1 npm i html-webpack-plugin -D
配置
Name
Type
Default
Description
title
{String}
Webpack App
The title to use for the generated HTML document
filename
{String}
'index.html'
The file to write the HTML to. Defaults to index.html
. You can specify a subdirectory here too (eg: assets/admin.html
)
template
{String}
``
webpack
relative or absolute path to the template. By default it will use src/index.ejs
if it exists. Please see the docs for details
templateParameters
`{Boolean
Object
Function}`
``
Allows to overwrite the parameters used in the template - see example
inject
`{Boolean
String}`
true
`true
‘head’
‘body’
falseInject all assets into the given
templateor
templateContent. When passing
trueor
‘body’all javascript resources will be placed at the bottom of the body element.
‘head’` will place the scripts in the head element - see the inject:false example
favicon
{String}
``
Adds the given favicon path to the output HTML
meta
{Object}
{}
Allows to inject meta
-tags. E.g. meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}
base
`{Object
String
false}`
false
Inject a base
tag. E.g. base: "https://example.com/path/page.html
minify
`{Boolean
Object}`
true
if mode
is 'production'
, otherwise false
Controls if and in what ways the output should be minified. See minification below for more details.
hash
{Boolean}
false
If true
then append a unique webpack
compilation hash to all included scripts and CSS files. This is useful for cache busting
cache
{Boolean}
true
Emit the file only if it was changed
showErrors
{Boolean}
true
Errors details will be written into the HTML page
chunks
{?}
?
Allows you to add only some chunks (e.g only the unit-test chunk)
chunksSortMode
`{String
Function}`
auto
Allows to control how chunks should be sorted before they are included to the HTML. Allowed values are `’none’
‘auto’
‘dependency’
‘manual’
{Function}`
excludeChunks
{Array.<string>}
``
Allows you to skip some chunks (e.g don’t add the unit-test chunk)
xhtml
{Boolean}
false
If true
render the link
tags as self-closing (XHTML compliant)
clean-webpack-plugin 1 npm i clean-webpack-plugin -D
1 npm i mini-css-extract-plugin -D
1 2 3 4 5 6 7 8 9 10 11 const MiniCssExtractPlugin = require ('mini-css-extract-plugin' ){ test: /.css$/ , use: [MiniCssExtractPlugin.loader, 'css-loader' ] } new MiniCssExtractPlugin({ filename: '[name][contenthash:8].css' })
sourceMap 源代码与打包后的代码的映射关系
在 development 模式中,默认开启,可以在配置文件中设置
devtool 的介绍:https://www.webpackjs.com/configuration/devtool/
eval: 速度最快,使用 eval 包裹模块代码
source-map:产生 .map
文件
cheap:较快,不用管列的信息,也不包含 loader 的 sourceMap
module:第三方模块,包含loader的sourceMap(比如 jsx to js,babel)
inline:将 .map
作为 DataURI 嵌入,不单独生成 .map
文件
配置推荐
1 2 3 devtool: 'cheap-module-eval-source-map' ; devtool: 'cheap-module-source-map' ;
WebpackDevServer 提升开发效率的利器
因为每次改完代码都需要重新打包一次,打开浏览器,刷新一次,很麻烦
安装 webpack-dev-server 改善体验
启动服务后,会发现 dist 目录为空,这是因为 DevServer 吧打包后的模块放到内存中,从而提升速度
1 npm i webpack-dev-server -D
修改 package.json
1 2 3 4 "script": { "server": "webpack-dev-server" }
配置
1 2 3 4 5 6 devServer: { contentBase: './dist' , open: true , port: 8081 }
跨域 联调期间,前后端分离,直接获取数据会跨域,上线后我们使用 Nginx 转发,开发期间,webpack 就能搞定
启动一个服务器,mock 一个借口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const express = require ('express' );const app = express();app.get('/api/info' , (req, res) => { res.json({ name: 'hhh' , age: 19 , msg: 'hello express' }) }) app.listen(9092 )
1 2 3 4 5 import axios from 'axios' axios.get('http://localhost:9092/api/info' ).then(res => { consol.elog(res) })
!!会产生跨域问题
修改 webpack.config.js 设置服务器代理
1 2 3 4 5 6 7 8 devServer: { proxy: { '/api' : { target: 'http://localhost:9092' } } }
修改 index.js
1 2 3 4 axios.get('/api/info' ).then(res => { console .log(res) })
搞定!
和服务器约定好接口,定义好字段
接口文档啥时候给到
根据接口文档mock数据,mock接口
文件监听 ? 轮询判断文件的最后编辑事件是否变化,某个文件发生变化,并不会立即告诉监听者,先缓存起来
webpack 开启监听模式,有两种
HMR Hot Module Replacement (热模块替换)
启动 HMR
1 2 3 4 5 6 7 devServer: { contentBase: './dist' , open: true , hot: true , hotOnly: true }
配置文件头部引入 webpack
1 2 3 4 5 6 const webpack = require ('webpack' )plugins: [ new webpack.HotModuleReplacementPlugin() ]
在插件配置处添加
1 2 3 4 5 6 7 plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: 'src/index.html' }), new Webpack.HotModuleReplacementPlugin() ]
案例:
1 2 3 4 5 6 7 8 9 10 11 12 import './css/index.css' ;let btn = document .createElement('button' );btn.innerHTML = 'add' ; document .body.appendChild(btn);btn.onclick = function ( ) { let div = document .createElement('div' ); div.innerHTML = 'item' ; document .body.appendChild(div); }
1 2 3 4 // index.css div :nth-of-type(odd) { background-color : yellow; }
注意:启动HMR后,css抽离会不生效,不支持 contenthash,chunkhash
处理 js 模块 HMR ? 需要使用 module.hot.accept 来观察模块更新
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function ( ) { let div = document .createElement('div' ); div.setAttribute('id' , 'counter' ); div.innerHTML = 1 ; div.onclick = function ( ) { div.innerHTML = parseInt (div.innerHTML, 10 ) + 1 ; } document .body.appendChild(div) } export default counter;function number ( ) { let div = document .createElement('div' ); div.setAttribute('id' , 'number' ); div.innerHTML = 12000 ; document .body.appendChild(div) } export default numberimport counter from './counter' ;import number from './number' ;counter() number() if (module .hot) { module .hot.accept('./b' , function ( ) { document .body.removeChild(document .getElementById('number' )); number(); }) }
Babel 官方网站:https://babeljs.io/
中文网站:https://www.babeljs.cn/
1 2 3 4 npm i babel-loader @babel/core @babel/preset-env -D
测试代码
1 2 3 4 5 6 const arr = [new Promise (() => { }), new Promise (() => { })]arr.map(item => { console .log(item) })
webpack.config.js
1 2 3 4 5 6 7 8 9 10 { test: /.js$/ , exclude: /node_modules/ , use: { loader: 'babel-loader' , options: { presets: ['@babel/preset-env' ] } } }
通过上面几部还不够,Promise 等一些特性还没有转换过来,这时候需要借助 @babel/polyfill
,把 es6 的新特性都装进来,来弥补低版本浏览器中缺失的特性
@babel/polyfill 以全局变量的方式注入进来,windows.Promise 会污染全局变量
1 npm i @babel/polyfill -S
1 2 3 4 5 6 7 8 9 { test: /.js$/ , exclude: /node_modules/ , loader: 'babel-loader' , options: { presets: ['@babel/preset-env' ] } }
1 2 import "@babel/polyfill"
虽然这样实现了,但是打包的体积大了不少,应为 polyfill 默认会把所有的特性注入进来,假如我想我?到的es6+,才会注?,没?到的不注?,从?减少打包 的体积,可不可以呢?
当然可以
修改 webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 options: { presets: [ [ "@babel/preset-env" , { targets: { edge: '17' , firefox: '60' , chrome: '67' , safari: '11.1' } }, corejs: 2 , useBuiltIns: 'usage' ] ] }
当开发的是组件库,工具库这些场景的时候,polyfill 就不合适了,因为 polyfill 是注入到 全局变量window下,会污染全局变量,所以推荐闭包方式:@babel/plugin-transform-runtime
它不会造成全局污染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 { "plugins" : [ [ "@babel/plugin-transform-runtime" , { "absoluteRuntime" : false , "corejs" : false , "helpers" : true , "regenerator" : true , "useESModule" : false } ] ] }
useBuiltIns
选项是 babel 7
的新功能,这个选项告诉 babel
如何配置 @babel/polyfill
三个参数
entry 需要在 webpack 的入口文件里 import '@babel/polyfill'
一次。babel
会根据你的使用情况导入垫片,没有使用的功能不会被导入相应的垫片
usage 不需要 import
,全自动检测,但是要安装 @babel/polyfill
(试验阶段)
false 如果你 import "@babel/polyfill"
,它不会排除掉没有使用的垫片,程序体积很庞大(不推荐)
请注意:usage 的行为类似 babel-tranform-runtime,不会造成 全局污染,因此也不会对类似 Array.prototype.includes() 进行 polyfill
扩展:
.babelrc 文件
新建 .babelrc 文件,把 options 部分移入到该文件中就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "plugins" : [ [ "@babel/plugin-transform-runtime" , { "absoluteRuntime" : false , "corejs" : false , "helpers" : true , "regenerator" : true , "useESModule" : false } ] ] } { test: /.js$/ , exclude: /node_modules/ , loader: 'babel-loader' }
配置 React 环境 安装 react 环境
1 npm i react react-dom -S
编写 react 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 import "@babel/polyfill" ;import React, { Component } from "react" ;import ReactDom from "react-dom" ;class App extends Component 大专栏 webpack an>{ render() { return <div > hello world</div > ; } } ReactDom.render(<App /> , document.getElementById("app"));
安装 babel 和 react 转换的插件
1 npm i @babel/preset-react --save-dev
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "preset" : [ [ "@babel/preset-env" , { targets: { edge: '17' , firefox: '60' , chrome: '67' , safari: '11.1' , Android: '6.0' } }, corejs: 2 , useBuiltIns: 'usage' ], "@babel/preset-react" ] }
扩展:多页面打包通用方案 1 2 3 4 5 6 7 8 9 10 11 entry:{ index: "./src/index" , list: "./src/list" , detail: "./src/detail" } new htmlWebpackPlugins({ title: "index.html" , template: path.join(__dirname, "./src/index/index.html" ), filename: "index.html" , chunks: [index] })
目录结构调整
使用 glob.sync 第三方库来匹配路径
1 const glob = require ('glob' )
1 2 3 4 5 6 7 8 9 10 const setMPA = () => { const entry = {}; const htmlWebpackPlugins = []; return { entry, htmlWebpackPlugins } } const { entry, htmlWebpackPlugins } = setMPA();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const setMPA = () => { const entry = {} const htmlWebpackPlugins = [] const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js' )) entryFiles.map((item, index ) => { const entryFile = entryFiles[index] const match = entryFile.match(/src/(.*)/index.js$/ ) const pageName = match && match[1 ] entry[pageName] = entryFile htmlWebpackPlugins.push( new htmlWebpackPlugin({ title: pageName, template: path.join(__dirname, `src/${pageName} /index.html` ), filename: `${pageName} .html` , chunks: [pageName], inject: true }) ) }) return { entry, htmlWebpackPlugins } } const { entry, htmlWebpackPlugins } = setMPA()module .exports = { entry, output: { path: path.resolve(__dirnmae, './dist' ), filename: '[name].js' }, plugins: [ ...htmlWebpackPlugins ] }
webpack 打包原理 webpack 在执?npx webpack进?打包后,都?了什么事情?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 (function (modules ) { var installedModules = {}; function __webpack_require__ (moduleId ) { if (installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false , exports: {} }; modules[moduleId].call( module .exports, module , module .exports, __webpack_require__ ); module .l = true ; return module .exports; } return __webpack_require__((__webpack_require__.s = "./index.js" )); })({ "./index.js" : function (module, exports ) { eval ( '// import a from "./a";nnconsole.log("hello word");nnn//# sourceURL=webpack:///./index.js?' ) }, "./a.js" : function (module, exports ) { eval ( '// import a from "./a";nnconsole.log("hello word");nnn//# sourceURL=webpack:///./index.js?' ) }, "./b.js" : function (module, exports ) { eval ( '// import a from "./a";nnconsole.log("hello word");nnn//# sourceURL=webpack:///./index.js?' ); } });
?概的意思就是,我们实现了?个webpack_require 来实现??的模块化,把代码都缓存在installedModules?,代码?件以对象传递进来,key 是路径,value是包裹的代码字符串,并且代码内部的require,都被替换成了webpack_require
实现一个 bundle.js
模块分析:读取入口文件,分析代码
1 2 3 4 5 6 7 8 const fs = require ('fs' )const moduleAnalyser = filename => { const content = fs.readFileSync(filename, 'utf-8' ) console .log(content) } moduleAnalyser('./index.js' )
拿到?件中依赖,这?我们不推荐使?字符串截取,引?的模块名越多,就越麻烦,不灵活,这?我们推荐使?@babel/parser,这是babel7的?具,来帮助我们分析内部的语法,包括es6,返回?个 ast 抽象语法树
@babel/parser https://babeljs.io/docs/en/babel-parser
安装 @babel/parser
1 npm i @babel/parser --save
bundle.js
1 2 3 4 5 6 7 8 9 10 11 12 13 const fs = require ('fs' )const parser = require ('@babel/parser' )const moduleAnalyser = filename => { const content = fs.readFileSync(filename, 'utf-8' ) const ast = parser.parse(content, { sourceType: 'module' }) console .log(ast.program.body) } moduleAnalyser('./index.js' )
接下来我们就可以根据body??的分析结果,遍历出所有的引?模 块,但是?较麻烦,这?还是推荐babel推荐的?个模块@babel/traverse,来帮我们处理。
1 npm i @babel/traverse --save
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const fs = require ('fs' )const path = require ('path' )const parser = require ('@babel/parser' )const traverse = require ('@babel/traverse' ).defaultconst moduleAnalyser = filename => { const content = fs.readFileSync(filename, 'utf-8' ) const ast = parser.parse(content, { sourceType: 'module' }) const dependecies = []; traverse(ast, { ImportDeclaration ({ node }) { console .log(node) dependecies.push(node.source.value) } }) console .log(dependecies) } moduleAnalyser('./index.js' )
最终结果图片
分析上图,我们要分析出信息:
入口文件
入口文件引入的模块
可以在浏览器中执行的代码
处理现在的路径问题
1 2 3 4 5 6 7 8 9 10 11 const parser = require ("@babel/parser" );const dependencies = {};const newfilename = "./" + path.join(path.dirname(filename), node.source.value);dependencies[node.source.value] = newfilename;
把代码处理成浏览器可运?的代码,需要借助@babel/core,和 @babel/preset-env,把ast语法树转换成合适的代码
1 2 3 4 5 const babel = require ('@babel/core' )const { code } = babel.transformFromAst(ast, null , { presets: ['@babel/preset-env' ] })
到处所有分析出的信息
1 2 3 4 5 return { filename, dependecies, code }
完成代码参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const fs = require ('fs' )const path = require ('path' )const parser = require ('@babel/parser' )const traverse = require ('@babel/traverse' ).defaultconst babel = require ('@babel/core' )const moduleAnalyser = filename => { const content = fs.readFileSync(filename, 'utf-8' ) const ast = parser.parse(content, { sourceType: 'module' }) const dependecies = {}; traverse(ast, { ImportDeclaration ({ node }) { const newfilename = './' + path.join(path.dirname(filename), node.source.value) dependecies[node.source.value] = newfilename } }) const { code } = babel.transformFromAst(ast, null , { presets: ['@babel/preset-env' ] }) return { filename, dependecies, code } } const moduleInfo = moduleAnalyser('./src/index.js' )console .log(moduleInfo)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 const fs = require ('fs' )const path = require ('path' )const parser = require ('@babel/parser' )const traverse = require ('@babel/traverse' ).defaultconst babel = require ('@babel/core' )const moduleAnalyser = filename => { const content = fs.readFileSync(filename, 'utf-8' ) const ast = parser.parse(content, { sourceType: 'module' }) const dependecies = {}; traverse(ast, { ImportDeclaration ({ node }) { const newfilename = './' + path.join(path.dirname(filename), node.source.value) dependecies[node.source.value] = newfilename } }) const { code } = babel.transformFromAst(ast, null , { presets: ['@babel/preset-env' ] }) return { filename, dependecies, code } } const makeDependenciesGraph = entry => { const entryModule = moduleAnalyser(entry) const graphArr = [entryModule] for (let i = 0 ; i < graphArr.length; i++) { const item = graphArr[i]; const { dependecies } = item if (dependecies) { for (let j in dependecies) { graphArr.push( moduleAnalyser(dependecies[j]) ) } } } const graph = {} graphArr.forEach(item => { graph[item.filename] = { dependecies: item.dependecies, code: item.code } }) return graph } const moduleInfo = makeDependenciesGraph('./src/index.js' )console .log(moduleInfo)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 const fs = require ('fs' )const path = require ('path' )const parser = require ('@babel/parser' )const traverse = require ('@babel/traverse' ).defaultconst babel = require ('@babel/core' )const moduleAnalyser = filename => { const content = fs.readFileSync(filename, 'utf-8' ) const ast = parser.parse(content, { sourceType: 'module' }) const dependecies = {}; traverse(ast, { ImportDeclaration ({ node }) { const newfilename = './' + path.join(path.dirname(filename), node.source.value) dependecies[node.source.value] = newfilename } }) const { code } = babel.transformFromAst(ast, null , { presets: ['@babel/preset-env' ] }) return { filename, dependecies, code } } const makeDependenciesGraph = entry => { const entryModule = moduleAnalyser(entry) const graphArr = [entryModule] for (let i = 0 ; i < graphArr.length; i++) { const item = graphArr[i]; const { dependecies } = item if (dependecies) { for (let j in dependecies) { graphArr.push( moduleAnalyser(dependecies[j]) ) } } } const graph = {} graphArr.forEach(item => { graph[item.filename] = { dependecies: item.dependecies, code: item.code } }) return graph } const generateCode = entry => { const graph = JSON .stringify(makeDependenciesGraph(entry)) return ` (function (graph) { function require (module) { function localRequire (relativePath) { return require(graph[module].dependencies[relativePath]) } var exports = {} (function (require, exports, code) { eval(code) })(localRequire, exports, graph[module].code) return exports } require('${entry} ') })(${graph} ) ` } const code = generateCode('./src/index.js' )console .log(code)
node 调试工具使用
修改 scripts
1 "debug": "ndoe --inspect --inspect-brk ndoe_modules/webpack/bin/webpack.js"
如何自己编写一个 Loader ??编写?个Loader的过程是?较简单的,
Loader就是?个函数,声明式函数 ,不能?箭头函数
拿到源代码,作进?步的修饰处理,再返回处理后的源码就可以了
官??档:https://webpack.js.org/contribute/writing-a-loader/
接??档:https://webpack.js.org/api/loaders/
简单案例
创建?个替换源码中字符串的loader
1 2 3 4 5 6 7 8 9 10 console .log('hello kkb' )module .exports = function (source ) { console .log(source, this , this .query) return source.replace('kkb' , '开课吧' ) }
在配置文件中使用 loader
1 2 3 4 5 6 7 8 9 10 const path = require ('path' )module : { rules: [ { test: /.js$/ , use: path.resolve(__dirname, './loader/replaceLoader.js' ) } ] }
如何给 loader 配置参数,loader 如何接受参数?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 module : { rules: [ { test: /.js$/ , use: [ { loader: path.resolve(__dirname, './loader/replaceLoader.js' ), options: { name: '开课吧' } } ] } ] } const loaderUtils = require ("loader-utils" ); module .exports = function (source ) { const options = loaderUtils.getOptions(this ); const result = source.replace("kkb" , options.name); return source.replace("kkb" , options.name); }
this.callback 如何返回多个信息,不只是处理好的源码,可以使用 this.callback 来处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const loaderUtils = require ("loader-utils" ); module .exports = function (source ) { const options = loaderUtils.getOptions(this ); const result = source.replace("kkb" , options.name); this .callback(null , result); }; err: Error | null , content: string | Buffer, sourceMap?: SourceMap, meta?: any );
this.async 如果 loader 里面有异步事件要怎么处理
1 2 3 4 5 6 7 8 9 const loaderUtils = require ("loader-utils" )module .exports = function (source ) { const options = loaderUtils.getOptions(this ) setTimeout(() => { return source.replace('kkb' , options.name) }, 1000 ) }
我们使用 this.async来处理,他会返回 this.callback
1 2 3 4 5 6 7 8 9 10 11 12 const loaderUtils = require ("loader-utils" )module .exports = function (source ) { const options = loaderUtils.getOptions(this ) const callback = this .async(); setTimeout(() => { const result = source.replace('kkb' , options.name) callback(null , result) }, 1000 ) }
多个 loader 的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 module .exports = function (source ) { return source.replace('开课吧' , 'word' ) } const loaderUtils = require ("loader-utils" )module .exports = function (source ) { const options = loaderUtils.getOptions(this ) const callback = this .async(); setTimeout(() => { const result = source.replace('kkb' , options.name) callback(null , result) }, 1000 ) } module : { rule: [ { test: /.js$/ , use: [ path.resolve(__dirname, './loader/replaceLoaderAsync.js' ), { loader: path.resolve(__dirname, './loader/replaceLoaderAsync.js' ), options: { name: '开课吧' } } ] } ] }
顺序,从上而下,从右到左
处理 loader 的路径问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 resolveLoader: { modules: ['node_modules' , './loader' ] }, module : { rules: [ { test: /.js$/ , use: [ 'replaceLoader' , { loader: 'replaceLoaderAsync' , options: { name: '开课吧' } } ] } ] }
参考:loader API https://webpack.js.org/api/loaders
如何自己编写一个 Plugins Plugin 开始打包,在某个时刻,帮助我们处理?些什么事情的机制
plugin要?loader稍微复杂?些,在webpack的源码中,?plugin的机制还是占有?常?的场景,可以说plugin是webpack的灵魂
设计模式
事件驱动
发布订阅
plugin是?个类,??包含?个apply函数,接受?个参数,compiler
官??档:https://webpack.js.org/contribute/writing-a-plugin/
案例:
参考:compiler-hooks https://webpack.js.org/api/compiler-hooks
tree shaking webpack 2.x 开始支持 tree shaking 概念,顾名思义,摇树,只支持ES module 的引入方式!!!!
1 2 3 4 optimization: { usedExports: true }
1 2 3 4 "sideEffects": false // 正常对所有模块进行 tree shaking 或 sideEffects ["*.css" , "@babel/polyfill" ]
开发模式设置后,不会帮助我们把没有引用的代码去掉
code spliting ? 代码分割,比如讲一些第三方库抽离,形成单独的文件
webpack
原文:https://www.cnblogs.com/lijianming180/p/12432958.html
This is copyright.