Open Skeanmy opened 4 years ago
webpack-dev-server --open --open 打开默认浏览器
Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。
resolve:{ alias:{ components: './src/components/' } }
当你通过 import Button from 'components/button 导入时,实际上被 alias 等价替换成了 import Button from './src/components/button'。
import Button from 'components/button
alias
import Button from './src/components/button'
presets:babel插件集合
presets
babel
babel-loader:代码转换成es5
babel-loader
@babel/core:核心库,必须有才能转,js转成抽象语法树AST
@babel/core
@babel/preset-env:自动识别浏览器版本以及代码版本,然后找到对应的库去转
@babel/preset-env
@babel/polyfill:写业务代码时使用,补充缺失的对象方法等,如map、Promise对象等,在window对象上绑定了一系列的方法。
@babel/polyfill
window.Promise window.map
@babel/plugin-transform-runtime:写库代码时使用,使用闭包形式引入es6的代码,打包第三方模块时使用,因为polyfill会污染全局变量
@babel/plugin-transform-runtime
module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: [ ['@babel/preset-env', { // 目标浏览器的版本号 targets: { chrome: '67' }, // 按需引入polyfill,如只写了map的语法,就不需要引入promise的polyfill useBuiltIns: 'usage' }] ] }] }
.babelrc:把options中的内容写到这个文件中去
.babelrc
@babel/perset-react:专门用于react
@babel/perset-react
// .babelrc { presets: [ [ "@babel/preset-env", { targets: { chrome: "67", }, useBuiltIns: 'usage' } ], "@babel/preset-react" ] }
babel配置文件也是从下往上,从右往左执行;上面配置文件的含义是先解析react代码为es6,再将es6转换成es5。
内存中打包,不设置publicPath时,默认将bundle.js输出到根目录,localhost:8000/bundle.js
localhost:8000/bundle.js
https://segmentfault.com/a/1190000005614604
webpack-cli
webpack
npx webpack index.js
npx webpack -v
npm info webpack
package.json
npx
entry默认打包输出的文件名称为main.js
main.js
output中可以对打包输出文件名进行更改
entry: { main: './src/index.js', sub: './src/index.js' }, output: { publicPath: 'http://cdn.com.cn', filename: '[name].js', path: path.resolve(__dirname, 'dist') }
在这种情况下,html-webpack-plugin会引入两个script文件
publicPath:添加打包出来文件的公共路径头,可以用于配置cdn
loader的执行是从下到上,从右到左的执行
用于打包图片、普通文本等文件,打包普通图片时会把打包后文件的目录返回;
通过options来增加配置
options
name:通过placeholder占位符进行打包文件的重命名
name
placeholder
outputPath:配置打包的文件位置
outputPath
打包字体文件
{ test: /\.(eot|ttf|svg)$/, use: { loader: 'file-loader' } }
可以实现file-loader的所有功能
file-loader
图片以base64格式直接打包到对应的img标签上,节省了一次http请求
base64
但是如果图片很大,bundle.js就会很大,增加白屏时间
bundle.js
module: { rules: [{ test: /\.(jpg|png|gif)$/, use: { loader: 'url-loader', options: { name: '[name]_[hash].[ext]', outputPath: 'images/', limit: 10240 } } }] },
增加配置项limit进行图片压缩配置
limit
import './style.css'
分析多个文件之间css的依赖关系
{ loader: 'css-loader', options: { importLoaders: 2 } }
解决了这种场景:在一个scss文件中使用了@import './a.scss'时直接通过css-loader从而解析错误的情况,引入了options后所有引入的css也要走postcss-loader和scss-loader。
@import './a.scss'
css-loader
css
postcss-loader
scss-loader
插入html头部的style标签
需要安装以下几个包
{ test: /\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader', 'postcss-loader' ] }
css3的样式需要写厂商前缀
css3
// postcss.config.js module.exports = { plugins: [ require('autoprefixer') ] }
在webpack的生命周期之中自动做一些事情,在webpack打包后在控制台的输出日志可以看到插件的运行情况。
打包之后运行
会在打包结束后自动生成html文件,并把打包生成的js文件自动引入到html文件中
plugins: [new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin(['dist'])],
根据src目录下的模板去生成dist目录的模板
打包之前运行
打包之前先把之前打包生成的文件删除
通过devtool进行配置
devServer: { // 服务器启动在哪个文件夹下面 contentBase: './dist', open: true, port: 8080 },
proxy: { // 这里可以写正则 '/react/api': { target: 'https://www.dell-lee.com', // 实现对https地址的转发 secure: false, pathRewrite: { 'header.json': 'demo.json' }, // bypass: 跳过的内容 // changeOrigin: 突破特定网站对origin的限制 changeOrigin: true, headers: { host: 'www.dell-lee.com', // 也可以在这里配置cookie } } }
devServer: { historyApiFallback: true } // 等价于,即对任何路径的请求转发到index.html上,根据它上面的业务逻辑去决定显示什么样的内容 devServer: { historyApiFallback: { rewrites: [{ from: /\.*\/, to: '/index.html' }] } }
在react或者vue开发时,请求路径发生变化时,会把这个路径的请求转换成为对根路径\的请求,而不会去请求服务器
\
如果服务器没有配置这个路由,则返回404
底层的实现是connect-history-api-fallback
只在本地有效,上线后需要后端进行nginx或者apache去配置转发。
nginx
apache
devServer: { contentBase: './dist', open: true, port: 8080, // 开启HMR功能 hot: true, // 即使HMR不生效,浏览器也不自动刷新 // HMR失效时不刷新页面,即这个配置只对HMR负责 hotOnly: true }, plugins: [ new webpack.HotModuleReplacementPlugin() ],
配置完HMR需要重启服务
// 如果开启了HMR功能 if(module.hot) { // 如果number.js内容变化,则执行回调函数 module.hot.accept('./number', () => { document.body.removeChild(document.getElementById('number')); number(); }) }
vue-loader
只打包引入的内容,别的没有使用的东西都不需要,通过“摇树”去除掉不需要的内容。
// 开发环境的配置 optimization: { usedExports: true },
// package.json // 对所有的模块进行treeshaking "sideEffects": false // 对特定模块不进行treeshaking "sideEffects": ["@babel/polyfill", "*.css"]
使用webpack-merge第三方模块进行webpack配置文件的合并
webpack-merge
Development:开发环境的sourceMap很全,线上环境就不需要那么全了
Production:需要代码压缩
一般来说会创建一个build目录来对存放两个环境的配置文件,这时候打包出来的dist目录会位于build这个文件夹下面,为了解决这个问题,需要对配置文件中的有关于路径的地方进行更改
plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), // 第一个参数数组中的路径是相对于根路径的路径 new CleanWebpackPlugin(['dist'], { // 配置根路径 root: path.resolve(__dirname, '../') }) ], output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') }
公用代码的拆分
每次请求2Mb的资源一般来说要比并行加载两个1Mb要慢
// 同步代码分割 optimization: { splitChunks: { chunks: 'all' } },
代码分割和webpack无关,在webpack中进行代码分割有两种方式:
optimization
import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
import()
then
Ctrl+Shift+p coverage
在线上环境为了解决缓存的问题,需要对每一次打包的文件进行hash命名
webpack是基于模块打包的,模块之间不耦合。
如a模块引入了jquery,b模块是一个库,其中使用了jquery,在a中引入了b,会出现b中无法找到jq的情况。
模块内的this指向的是模块本身,不是window对象
eslint
npx eslint --init
eslint src
module.exports = { "extends": "airbnb", // 通用的eslint解析器 "parser": "babel-eslint", "rules": { "react/prefer-stateless-function": 0, "react/jsx-filename-extension": 0 }, globals: { document: false } };
module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: ['babel-loader', 'eslint-loader'] }] }, devServer: { // 打开浏览器黑窗口报告错误 overlay: true, },
eslint-loader
https://webpack.js.org/loaders/eslint-loader/#install
module.exports = { entry: '...', module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'eslint-loader', options: { cache: true, }, force: 'pre' }, 'babel-loader' ], }, };
force: pre
git
跟上技术迭代:Node、Npm、Yarn升级
Node
Npm
Yarn
在尽可能少的模块上应用Loader
loader
exclude
include
少使用Plugin,确保插件的质量,多使用官方推荐的插件
Plugin
resolve参数合理配置
resolve
resolve: { extensions: ['.js', '.jsx'], // 配置目录下的入口文件 mainFiles: ['index', 'xxx'], // 配置别名 alias: { child: path.resolve(__dirname, '../src/a/b/c/child') } },
每一次resolve都是文件查找操作,很耗时。
使用DllPlugin提高打包速度
DllPlugin
webpack.DllPlugin
webpack.DllReferencePlugin
打包好的dll文件要挂在到html上,注册一个全局变量,如react、jquery、loadsh等
控制包文件大小
thread-loader,parallel-webpack、happypack多进程打包
thread-loader
parallel-webpack
happypack
合理使用sourceMap
sourceMap
结合stats分析打包结果,stats.json
stats
stats.json
开发环境内存编译
开发环境中剔除无用的插件
单页面应用:只有一个html文件
jquery、zepto等旧项目需要进行多页面打包
jquery
zepto
使用html-webpack-plugin
实质是增加entry内容,底下配合着添加HtmlWebpackPlugin
entry
HtmlWebpackPlugin
const makePlugins = (configs) => { const plugins = [ new CleanWebpackPlugin(['dist'], { root: path.resolve(__dirname, '../') }) ]; Object.keys(configs.entry).forEach(item => { plugins.push( new HtmlWebpackPlugin({ template: 'src/index.html', filename: `${item}.html`, chunks: ['runtime', 'vendors', item] }) ) }); const files = fs.readdirSync(path.resolve(__dirname, '../dll')); files.forEach(file => { if(/.*\.dll.js/.test(file)) { plugins.push(new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(__dirname, '../dll', file) })) } if(/.*\.manifest.json/.test(file)) { plugins.push(new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, '../dll', file) })) } }); return plugins; }
const path = require('path'); module.exports = { mode: 'development', entry: { main: './src/index.js' }, // 使用loader时会触发这个配置 resolveLoader: { // 先去node_modules目录下面去找,再去loaders目录去找 modules: ['node_modules', './loaders'] }, module: { rules: [{ test: /\.js/, use: [ { loader: 'replaceLoader', }, { loader: 'replaceLoaderAsync', options: { name: 'lee' } }, ] }] }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' } }
通过this.query进行传递参数,拿到options配置中的内容:this.query.name
this.query
this.query.name
通过this.callback传递loader分析后的错误、SourceMap
this.callback
this.callback( err: Error | null, content: string | Buffer, sourceMap?: SourceMap, meta?: any );
处理异步逻辑时调用this.async()方法
this.async()
const loaderUtils = require('loader-utils'); module.exports = function(source) { const options = loaderUtils.getOptions(this); const callback = this.async(); setTimeout(() => { const result = source.replace('dell', options.name); callback(null, result); }, 1000); }
AST
function
try...catch...
{{title}}
compile
cb
emit
debugger
npm run debug --> node --inspect --inspect-brk node_modules/webpack/bin/webpack.js
--inspect
--inspect-brk
class CopyrightWebpackPlugin { apply(compiler) { compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => { console.log('compiler'); }) // compilation本次打包的内容 compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => { debugger; compilation.assets['copyright.txt']= { // 文件内容 source: function() { return 'copyright by dell lee' }, // 文件大小 size: function() { return 21; } }; cb(); }) } } module.exports = CopyrightWebpackPlugin;
@babel/parser
@babel/traverse
const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); // 默认导出内容是esmodule的导出,需要加一个default const traverse = require('@babel/traverse').default; const babel = require('@babel/core'); const moduleAnalyser = (filename) => { const content = fs.readFileSync(filename, 'utf-8'); // 把js代码转换成为一个js对象 const ast = parser.parse(content, { // esmodule模式 sourceType: 'module' }); const dependencies = {}; traverse(ast, { // 有引入代码时走下面的内容 ImportDeclaration({ node }) { const dirname = path.dirname(filename); // 相对路径转绝对路径 const newFile = './' + path.join(dirname, node.source.value); dependencies[node.source.value] = newFile; } }); // 使用babel进行代码转换 const { code } = babel.transformFromAst(ast, null, { presets: ["@babel/preset-env"] }); return { filename, dependencies, code } } // 递归分析文件依赖 const makeDependenciesGraph = (entry) => { const entryModule = moduleAnalyser(entry); const graphArray = [ entryModule ]; for(let i = 0; i < graphArray.length; i++) { const item = graphArray[i]; const { dependencies } = item; if(dependencies) { for(let j in dependencies) { graphArray.push( moduleAnalyser(dependencies[j]) ); } } } const graph = {}; graphArray.forEach(item => { graph[item.filename] = { dependencies: item.dependencies, 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');
npm run eject
OneOf
npm install -g @vue/cli # OR yarn global add @vue/cli # 检查版本 vue --version vue create my-app
没有暴露webpack配置项,但是提供了一套简洁的api(vue.config.js)方便用户去配置webpack
实际上底层会把vue.config.js中的内容翻译成webpack的配置
vue.config.js
\node_modules\@vue\cli-service\lib\Service.js把vue配置参数转换成为webpack的配置文件
\node_modules\@vue\cli-service\lib\Service.js
webpack
分别介绍bundle,chunk,module是什么
resolve
Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。
当你通过
import Button from 'components/button
导入时,实际上被alias
等价替换成了import Button from './src/components/button'
。babel
presets
:babel
插件集合babel-loader
:代码转换成es5@babel/core
:核心库,必须有才能转,js转成抽象语法树AST@babel/preset-env
:自动识别浏览器版本以及代码版本,然后找到对应的库去转@babel/polyfill
:写业务代码时使用,补充缺失的对象方法等,如map、Promise对象等,在window对象上绑定了一系列的方法。@babel/plugin-transform-runtime
:写库代码时使用,使用闭包形式引入es6的代码,打包第三方模块时使用,因为polyfill会污染全局变量.babelrc
:把options中的内容写到这个文件中去@babel/perset-react
:专门用于reactbabel配置文件也是从下往上,从右往左执行;上面配置文件的含义是先解析react代码为es6,再将es6转换成es5。
devServer
内存中打包,不设置publicPath时,默认将bundle.js输出到根目录,
localhost:8000/bundle.js
webpack dev middleware原理
https://segmentfault.com/a/1190000005614604
课程学习
webpack-cli
会自动安装webpack
,可以在命令行中使用webpack命令npx webpack index.js
npx webpack -v
:在当前目录查找webpacknpm info webpack
:查看webpack存在的版本package.json
中的script中不用写npx
,默认会在本地文件夹内先找对应的包entry和output
entry默认打包输出的文件名称为
main.js
output中可以对打包输出文件名进行更改
在这种情况下,html-webpack-plugin会引入两个script文件
publicPath:添加打包出来文件的公共路径头,可以用于配置cdn
loader
file-loader
用于打包图片、普通文本等文件,打包普通图片时会把打包后文件的目录返回;
通过
options
来增加配置name
:通过placeholder
占位符进行打包文件的重命名outputPath
:配置打包的文件位置打包字体文件
url-loader
可以实现
file-loader
的所有功能图片以
base64
格式直接打包到对应的img标签上,节省了一次http请求但是如果图片很大,
bundle.js
就会很大,增加白屏时间增加配置项
limit
进行图片压缩配置css-loader
分析多个文件之间css的依赖关系
解决了这种场景:在一个scss文件中使用了
@import './a.scss'
时直接通过css-loader
从而解析错误的情况,引入了options
后所有引入的css
也要走postcss-loader
和scss-loader
。style-loader
插入html头部的style标签
sass-loader
需要安装以下几个包
postcss-loader
css3
的样式需要写厂商前缀plugin
在webpack的生命周期之中自动做一些事情,在webpack打包后在控制台的输出日志可以看到插件的运行情况。
html-webpack-plugin
打包之后运行
会在打包结束后自动生成html文件,并把打包生成的js文件自动引入到html文件中
根据src目录下的模板去生成dist目录的模板
clean-webpack-plugin
打包之前运行
打包之前先把之前打包生成的文件删除
SourceMap
通过devtool进行配置
webpackDevServer
实现请求转发
实现单页面应用跳转
在react或者vue开发时,请求路径发生变化时,会把这个路径的请求转换成为对根路径
\
的请求,而不会去请求服务器如果服务器没有配置这个路由,则返回404
底层的实现是connect-history-api-fallback
只在本地有效,上线后需要后端进行
nginx
或者apache
去配置转发。配置完HMR需要重启服务
css-loader
底层实现了css HMR的功能vue-loader
底层实现了vue文件HMR的功能Tree Shaking
只打包引入的内容,别的没有使用的东西都不需要,通过“摇树”去除掉不需要的内容。
两种打包模式
使用
webpack-merge
第三方模块进行webpack配置文件的合并Development:开发环境的sourceMap很全,线上环境就不需要那么全了
Production:需要代码压缩
一般来说会创建一个build目录来对存放两个环境的配置文件,这时候打包出来的dist目录会位于build这个文件夹下面,为了解决这个问题,需要对配置文件中的有关于路径的地方进行更改
Code Spliting(代码分割)
公用代码的拆分
代码分割和webpack无关,在webpack中进行代码分割有两种方式:
optimization
做代码分割import()
加载模块成功以后,这个模块会作为一个对象,当作then
方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。打包分析,Preloading,Prefetch
Ctrl+Shift+p coverage
缓存问题
在线上环境为了解决缓存的问题,需要对每一次打包的文件进行hash命名
Shimming(垫片)
webpack是基于模块打包的,模块之间不耦合。
如a模块引入了jquery,b模块是一个库,其中使用了jquery,在a中引入了b,会出现b中无法找到jq的情况。
模块内的this指向的是模块本身,不是window对象
库的打包
ts的打包
PWA的打包
EsLint的配置
eslint
配置webpack中配置eslint
eslint-loader
配置https://webpack.js.org/loaders/eslint-loader/#install
eslint-loader
一定要在babel-loader
前面执行,如果eslint-loader
放在了前面,需要配置force: pre
这个选项。最佳实践
eslint-loader
会降低打包速度git
钩子进行优化,提交代码时才会进行代码检查性能优化--提升打包速度
跟上技术迭代:
Node
、Npm
、Yarn
升级在尽可能少的模块上应用Loader
loader
里使用exclude
,减少额外的代码翻译include
限定语法转换的范围少使用
Plugin
,确保插件的质量,多使用官方推荐的插件resolve
参数合理配置每一次
resolve
都是文件查找操作,很耗时。使用
DllPlugin
提高打包速度webpack.DllPlugin
webpack.DllReferencePlugin
打包好的dll文件要挂在到html上,注册一个全局变量,如react、jquery、loadsh等
控制包文件大小
thread-loader
,parallel-webpack
、happypack
多进程打包合理使用
sourceMap
结合
stats
分析打包结果,stats.json
开发环境内存编译
开发环境中剔除无用的插件
多页面打包(4-11)
单页面应用:只有一个html文件
jquery
、zepto
等旧项目需要进行多页面打包使用html-webpack-plugin
实质是增加
entry
内容,底下配合着添加HtmlWebpackPlugin
自定义loader
通过
this.query
进行传递参数,拿到options配置中的内容:this.query.name
通过
this.callback
传递loader分析后的错误、SourceMap处理异步逻辑时调用
this.async()
方法loader能够实现的功能--解决源代码的包装问题
AST
分析,如果出现了function
字段,就使用try...catch...
将代码包裹起来{{title}}
替换成为对应的版本内容即可。自定义plugin
compile
,同步钩子没有回调方法cb
emit
debugger
打断点npm run debug --> node --inspect --inspect-brk node_modules/webpack/bin/webpack.js
--inspect
是启动node的调试工具--inspect-brk
指的是执行webpack.js时在文件的第一行打上断点自定义打包工具
@babel/parser
分析源代码,把js代码转换成为一个js对象@babel/traverse
:遍历AST,对声明、表达式、模块引入等不同的语法进行识别,并且进行后续的操作脚手架配置
CreateReactApp
npm run eject
:把隐藏的配置文件释放出来OneOf
这个字段的配置是对特定后缀的文件进行准确匹配loader,仅仅一次,节约资源VueCli
没有暴露webpack配置项,但是提供了一套简洁的api(vue.config.js)方便用户去配置webpack
实际上底层会把
vue.config.js
中的内容翻译成webpack的配置\node_modules\@vue\cli-service\lib\Service.js
把vue配置参数转换成为webpack的配置文件