现有引入方式的区分:
- 1、es moudle的模块引入方式
import header from './header.js'
- 2、common js的模块引入方式
const header = require('./header.js')
- 3、CMD的模块引入方式
@import url("fineprint.css") print
- 4、AMD的模块引入方式
define([name: String], [dependencies: String[]], factoryMethod: function(...));
- 5、样式中的引入
url(...)或者<img src="..">
webpack 是什么
- 1、webpack是1个模块打包工具,module bundler.
- 2、npx 可以在当前文件夹中使用。
- 3、webpack-cli使得我们在命令行中可以使用webpack命令。(cli:command-line interface命令行界面)
- 4、chunks指的是每个文件对应的id,chunk names指的是每个文件对应的名字。
loader 是什么
- webpack默认只编译js文件
- loader的执行顺序是从下到上,从右到左
- 1、url-loader和file-loader都是编译jpg|png|gif等图片文件,url-loader比style-loader多个功能,可以在options设置limit,小于limit,就转成base64,大于就直接导出
- 2、css-loader和style-loader可以编译css文件,css-loader会找到所有的css文件,编译css文件,style-loader转成style挂载在head标签中的style标签中
- 3、sass-loader编译scss文件
plugins是什么
- plugin 可以在webpack运行到某个时刻的时候,帮你打包一些事情。
- htmlWebpackPlugin 会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中。
- cleanWebpackPlugin 会在打包之前先执行。htmlWebpackPlugin是在打包之后再运行。
sourceMap是什么
- sourceMap是一个映射关系,他知道打包生成的dist目录中的某一行对应源代码中的哪一些,通过sourceMap可以知道对应源代码中的某一行。从而更方便调试。
- inline-source-map 当使用时,生成的.map文件会变成base64文件集合到js里面。
- cheap-inline-source-map 知道是哪一行出错而不用知道是哪一列,减少性能消耗。
- cheap-module-inline-source-map 加了module主要作用是不仅管业务代码中的报错,还负责第三方模块中的报错。
- eval 不是通过base64映射对应关系,而是通过eval去映射,执行效率最快,性能最好。但是在比较复杂的代码情况下,提示出来的内容可能不全面。
- 最佳实践配置:在mode为‘development’模式下,
cheap-module-eval-source-map
,配置最全,打包速度最快; 在mode为‘production’模式下,使用cheap-module-source-map
配置更好。 - sourceMap的原理:
- segmentfault.com/a/119000000… www.html5rocks.com/en/tutorial… www.ruanyifeng.com/blog/2013/0… www.youtube.com/watch?v=NkV…
- sourceMap使用VLQ编码。
webpackDevServer提升开发效率
- 改变代码,线上打包就会自动生效的几种方式
- 1、webpack --watch 不会起一个服务器,而且不会重新起浏览器,比较麻烦,所以不太建议使用。
- 2、使用webpack-dev-server 启动1个http服务器,还会帮助我们打包代码,不会输出到dist目录,而是在内存中。
- 3、使用webpackDevMiddleware结合express()进行处理。
- 在node中直接使用webpack
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack.config.js')
// 在node中直接使用webpack
const compiler = webpack(config)
const app = express()
app.use(webpackDevMiddleware(compiler, {}))
app.listen(3000, () => {
console.log('server is running.')
})
Hot Module Replacement 热模块替换,hmr:热更新
- 可以在我们写css的时候,帮忙调试css
babel-loader
- 可以将es6的代码转换成es5的代码
- 如果写的是业务代码,那么需要引入@babel/polyfill,并且配置
@babel/preset-env
- 如果写的是第三方库代码,那么需要引入@babel/plugin-transform-runtime,并且配置plugins
- 示例代码:
options: {
// 这里要引入@babel-polyfill
presets: [['@babel/preset-env', {
targets: {
chrome: '67'
},
useBuiltIns: 'usage'
}]]
// 这里不用引入@babel-polyfill
plugins: [['@babel/plugin-transform-runtime', {
'corejs': 2,
'helpers': true,
'regenerator': true,
'useESModules': false
}]]
}
配置react代码的打包
- 使用
@babel/preset-react
如:
{
presets: [['@babel/preset-react']]
}
devServer
devServer: { // 可以启动一个服务器运行
contentBase: path.resolve(__dirname, 'dist'), //在哪个目录中为服务器提供内容。
compress: true, // 使用gzip进行压缩
port: 9000, // 端口号
hot: true, // 启动webpack模块的热替换功能
hotOnly: true // 去掉hotOnly配置可以不用在浏览器上手动刷新页面,修改内容后会自动刷新。
}
Tree Shaking
- tree-shaking 指的是当引入一个模块的时候,不引入模块的所有代码,只引入某一块代码。
- Tree Shaking 只支持es module,不支持common js方式,require的引入
development和production的区别
- sourceMap在development的配置比较全,可以帮助我们在开发环境下快速定位代码的报错位置。在production环境下比较简洁一些,或者可以生成一个map文件来存储。
- 在development环境下打包生成的代码不需要做压缩,可以看到里面的说明项,production下一般是被压缩过的代码
optimization
optimization: {
uesdExports: true // 可以使我们的代码在一些不必要倒入导入的时候可以不导入,比如import '@css/style.css'
}
package.json
{
// 没有要做按需导入的其他模块,否则要填['style.css','@babel/polly-fill']去配置识别里面的模块
"sideEffects": false,
}
code splitting 代码分割。可以分成几个文件加载,提高性能
- 代码分割,和webpack无关
- webpack中的代码分割可以用2种方式
- 1、同步方式:可以使用以下代码进行配置,自动帮你代码分割
optimization: { splitChunks: { chunks: 'all' // 默认值是async,只对异步代码进行代码分割。 } }
- 2、异步方式:import('xxx').then(xxx),无需配置,自动帮你分割。
- 3、splitChunksPlugin参数讲解
optimization: { splitChunks: { chunks: 'async', // 'async'只对异步代码进行代码分割,'all'对同步和异步的代码都进行代码分割,'initial'对同步代码进行代码分割 minSize: 30000, // 引入的代码库大于30kb才做代码分割,小于就不做代码分割。 maxSize: 0, // 配置50000,在大于50000b的时候就可以分成更多的vendors,不过一般不配。 minChunks: 1, // 当一个模块被`引用`至少多少次才能被代码分割,1代表至少被引用1次 maxAsyncRequests: 5, // 所有的分割模块最多能分割出5个js文件 maxInitialRequests: 3, // 入口文件最多只能分割出3个js文件 automaticNameDelimiter: '~', // 组和文件之间连接时用~做连接符 name: true, // 在cacheGroup中起的名字有效 cacheGroups: { // 打包同步代码时,分割的代码放在哪个文件里面 vendors: { test: /[\\/]node_modules[\\/]/, // 意思是代码是在node_modules中引入的,就打包一个另一个文件。并在verdors组里,所以生成的文件名叫:vendors-main.js,main就是你的入口名。 priority: -10, // 值越大,优先级越高。 filename: 'vendors.js' // 设置保存的名字就叫vendors不叫vendors-main.js }, default: { // 默认走default minChunks: 2, priority: -20, reuseExistingChunk: true, // 如果一个模块被打包过了,在打包的时候就忽略这个模块。直接使用之前被打包过的模块 filename: 'common.js' // 改名字为common.js,默认放在common.js里面 } } } }
loadsh 是一个高性能使用一次功能函数。可以处理字符串等函数。
Lazy Loading 懒加载
async function getComponent() {
return import(/* webpackChunkName:"loadsh" */ 'loadsh').then(res => {
var element = document.createElement('div')
element.innerHTML = _.join(['a', 'b', 'c'])
return element
})
// 上面的代码也可以改写成以下代码
const { default: _ } = await import(/* webpackChunkName:"loadsh" */ 'loadsh')
const element = document.createElement('div')
element.innerHTML = _.join(['a', 'b', 'c'])
return element
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
- 上面这种方式就是懒加载,在页面刚开始的时候不会加载loadsh这个文件,只有在点击之后才会加载,实现页面的懒加载,使刚加载页面的时候加载速度更快。
- 懒加载不是webpack的概念,es提出的概念。
chunk是什么?
- 打包生成的每一个js文件都是chunk
打包分析
- github.com/webpack/ana… webpack分析工具的git仓库
- webpack.docschina.org/guides/code… 可以帮助我们分析打包生成的代码
preloading、prefetching---code coverge
- 可以帮助我们在浏览器空闲的时候缓存将来要加载的内容。
- 使用如下:
document.addEventListener('click', () => {
import(/* webpackPrefetch: true*/ './click.js').then(({default: func}) => {
func()
})
})
coverge分析:
打开chrome控制台,输入command+shift+p健,输入coverage.
css文件的代码分割
- 使用插件MiniCssExtraPLugin,只能运行在线上环境中。不支持hmr
- optimize-css-assets-webpack-plugin css代码合并和压缩
- 在optimization中配置
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
// 对所有同步还是异步加载的css文件,都打包到名为styles的文件中
enforce: true
// 忽略掉之前的minSize,maxSize参数的区分。
}
}
}
webpack与浏览器缓存
- 在打包生成的main.xxx.js和vendor.xxx.js中,生成了一个runtime.xxx.js,因为配置了以下内容。
optimization: {
runtimeChunk: {
name: 'runtime'
}
}
在main.js和vendor第三方库之间的关联代码叫manifest
- 在output中配置contenthash帮助浏览器坐缓存。 代码如下:
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
shimming shim 预置依赖,解决打包过程中的兼容性问题。
- 使用
webpack.ProvidePlugin
进行配置。来提供全局模块
new webpack.ProvidePlugin({
$: 'jquery'
})
- 使用
imports-loader
来将每个模块中的this指向全局变量window,使用示例:
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader?this=>window'
// 通过配置,可以使this指向全局变量。
}]
}]
}
打包线上环境
webpack.config.js的配置
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
// externals: ["lodash"], // 第一种配置方式
// externals: {
// lodash: {
// root: '_', // 通过全局script标签引入,并在页面中注入_为全局变量的lodash
// commonjs: 'lodash'
// }
// }, // 第二种配置方式
external: 'lodash', // 第三种配置方式,通过`import lodash from lodash`去引入
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'root',
// 打包生成的代码挂载到全局变量上,可以通过<script src="library"></script>方式去引入,配置root可以通过root变量去引入
libraryTarget: 'umd'
// 通过任何方式去引入库,都可以正常引用得到,填this->this.library,window->window.library,global->global.library去引入
}
}
pwa的打包配置
- http-server生成一个服务器,然后在本地启动。
- pwa的作用,第一次访问的时候,有个服务器可以访问,当服务器停掉的时候,可以使用缓存进行处理。
- 使用pwa的插件:workbox-webpack-plugin
使用示例:
在config文件中配置serviceworker
const WorkboxPlugin = require("workbox-webpack-plugin")
plugins: [
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
],
在业务代码中应用serviceworker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('./service-worker.js')
.then(registration => {
console.log('service-worker registed')
}).catch(error => {
console.log('service-worker register error')
})
})
}
这样可以让我们的页面放在缓存中。
Typescript的打包配置
- typescript是javascript的一个超集
- 需要额外配置tsconfig.json的文件
{
"compilerOptions": {
"outDir": "./dist", // 输出目录
"module": "es6", // 使用es6
"target": "es5", // 打包输出es5语法的文件代码类型
"allowJs": true // 允许在ts中引入js模块
}
}
- 需要使用外部库,同时实现可以提示外部库的一些报错信息。就需要引入外部库的相应ts类型文件
- 比如:import _ from 'lodash'
- 就要引入@types/lodash
WebpackDevServer实现请求转发
- 在mode为development环境下,在devServer下配置proxy进行转发,在线上环境中没有devServer,所以一般不会生效
module.exports = {
mode: 'development',
devServer: {
xxx,
proxy: { // 在开发过程中做接口转发
'/react/api': { // 当识别到是这个路径时
target: 'http://www.dell-lee.com', // 将当前的localhost转发到这这个域名
pathRewrite: {
'header.json': 'demo.json'
}
// 当header.json路径下还没开发好,可以转发到demo.json这个文件。在开发好后再去掉这个配置
}
}
}
}
- 当请求是https的时候就要使用secure: false,如以下代码:
module.exports = {
mode: 'development',
devServer: {
xxx,
proxy: {
'/react/api': {
target: 'https://www.dell-lee.com', // 这里使用了https
secure: false, // 当使用https时,这里就要配置secure,因为默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。
pathRewrite: {
'header.json': 'demo.json'
}
}
}
}
}
- 使用bypass还可以进行一次拦截。如以下代码:
module.exports = {
mode: 'development',
devServer: {
xxx,
proxy: {
index: '', // 默认proxy不支持根目录的转发,所以要使用index进行配置
'/react/api': {
target: 'http://www.dell-lee.com',
context: ['/auth', '/api'], // 如果有多个路径可以这么处理
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf("html") !== -1) {
console.log("Skipping proxy for browser request.");
return "/index.html";
}
}
},
changeOrigin: true // 可以防止网站对origin的转发进行限制。
headers: { // 可以对请求的headers进行转发。
host: 'www.dell-lee.com',
cookie: 'asd'
}
}
}
}
WebpackDevServer 解决单页面应用路由问题
- devServer中配置:historyApiFallback参数
- devServer只能在开发环境中使用
- 代码示例如下:
devServer: {
historyApiFallback: true
}
// 也可以配置rewrite,如以下:
devServer: {
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
// 遇到以/开头和结尾的文件,直接路由到/views/loading.html文件中
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' }
]
}
}
eslint在webpack中的配置
- eslint配置在项目中快速初始化可以用:
npx eslint --init
- 使用步骤,在项目中安装eslint->安装eslint-loader->在devServer中配置overlay为true->在module中配置eslint-loader
devServer: {
overlay: true // 这个配置可以直接在启动时直接告知打包时候遇到的问题
}
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader'] // 在这里配置eslint-loader
}
}
- 以下可以自动帮你修复代码
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', {
loader: 'eslint-loader',
options: {
fix: true, // 这里配置可以自动帮我们配置掉代码格式的问题
force: 'pre' // 强制eslint先执行
}
}]
}
}
提升webpack打包速度
- 1、跟上技术的迭代,升级webpack/node/npm/yarn的版本
- 2、在尽可能少的模块上应用loader,比如以下代码:
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/, // 这里使用exclude不去编译node_module中的代码,因为里面是已经打包编译过的,就可以提高打包编译速度。
include: path.resolve(__dirname, '../src') // 设置只有src文件夹下才使用babel-loader
use: [{
loader: 'babel-loader'
}]
}
- 3、plugin尽可能精简并确保可靠
- 最好使用官方插件或者社区验证过的插件
- plugin要尽可能少的使用
- 4、resolve参数合理配置
// 比如在配置中使用
module.exports = {
resolve: {
extensions: ['.js', '.jsx'], // 这里配置的含义是在页面中引用其他组件时,帮助你在那个目录下去首先找js结尾的文件,再去找以jsx结尾的文件。
mainFiles: ['index', 'child'] // 这里配置的含义是,当引入child/目录时,默认去找child下的index.js文件,找不到再去找child.js文件
alias: {
dellee: path.resolve(__dirname, '../src/child') // 这里使用别名,当引用delle这个别名的时候,指向src下的child目录。当写`import child from dellee`去引入第三方库时
}
}
}
resolve虽然好用,但是不能滥用。要合理配置,不然会降低webpack的打包速度。
- 5、使用DllPlugin提高打包速度
- 步骤:
- (1)、首先使用一个
webpack.dll.js
,来将第三方插件引入都加载打包为一个xx.dll.js文件 - (2)、使用
DllPlugin
来生成一个相应的manifest.json文件,映射到webpack.dll.js文件中。使得我们引用第三方模块的时候,只使用dll文件引入,只引用1次。
// webpack.dll.js 文件配置 const path = require('path'); const webpack = require('webpack'); module.exports = { mode: 'production', entry: { vendors: ['lodash'], react: ['react', 'react-dom'], jquery: ['jquery'] }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, '../dll'), library: '[name]' // 这个配置可以生成一个全局变量,名字为name }, plugins: [ // 使用Dllplugin new webpack.DllPlugin({ name: '[name]', path: path.resolve(__dirname, '../dll/[name].manifest.json'), }) ] }
- (3)、在项目的配置中使用
DllReferencePlugin
来生成xxx.manifest.json文件进行映射。使得项目不去node_modules中使用第三方插件,而去xxx.dll.js文件中去找,从而实现打包速度的提升。
// webpack.common.js中新增的配置 plugins: [ new AddAssetHtmlWebpackplugin({ filepath: path.resolve(__dirname, '../dll/vendors.dll.js') }) // 这里借助AddAssetHtmlWebpackplugin插件来全局变量vendors挂载到windows中。 new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, '../dll/vendors.manifest.json') }) ]
- (4)、上面entry中有多个入口名,那就会生成多个xxx.dll.js和xxx.manifest.json文件,从而使得plugin中的配置要多配介个new AddAssetHtmlWebpackplugin和new webpack.DllReferencePlugin.这时就可以使用函数来进行通用处理,从而使配置更灵活些。
// 代码示例: const plugins = [ // 这里是固定的plugin插件 new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin(['dist'], { root: path.resolve(__dirname, '../') }) ]; // 下面通过node模块引入fs文件读取dll文件夹下的内容,然后进行匹配处理,从而达到更加只能。 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) })) } }) module.exports = { xxx, plugins, // 直接去引入 }
- 6、控制包文件大小
- 在项目中不需要的第三方模块要使用tree-shaking去控制或者不去引入,从而避免打包生成的文件太大。
- 也可以使用splitChunkPlugin插件来对代码进行拆分。
- 7、webpack是基于node的,所以是单进程的,我们可以使用
thread-loader
多进程打包,或者paraller-webpack
进行多页面打包,happypack
等来进行多进程打包,从而提高打包速度。 - 8、合理使用sourceMap,因为sourceMap越详细,打包的速度越慢,所以要合理应用,在不同环境打包的时候,使用不同的配置
- 9、结合stats分析打包结果。
- 10、开发环境使用内存编译
- 11、开发环境无用插件剔除
多页面的webpack配置
- 原理:在entry文件增加多个入口js文件,使用
new HtmlWebpackPlugin
配置生成多个html,实现多页面打包的流程。。
// 代码示例:
const makePlugins = (configs) => {
// 这里是plugin的默认配置
const plugins = [
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
];
// 下面通过获取configs中的entry来循环使用htmlWebpackplugin生成不同的文件,从而可以实现打包多页面的功能
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 configs = {
// 这里每使用1个新的页面,新增一个新的入口即可。
entry: {
index: './src/index.js',
list: './src/list.js',
detail: './src/detail.js',
}
}
configs.plugins = makePlugins(configs);
module.exports = configs
编写自己的loader
- 1、什么情况下可以自己编写loader
- (1)、异常捕获
- (2)、可以使用loader来进行国际化。
loader和plugin的区别
- 当需要引用js文件或者其他格式的文件时,可以使用loader去帮助处理这个文件,帮助我们去处理模块
- plugin在我们打包的时候,就会生效,比如打包生成个html文件,就使用htmlWebpackplugin,当打包之前需要清理文件夹,就使用cleanWebpackPlugin
- loader本质上一个函数,plugin本质上是1个类
bail
- 一旦在打包过程中遇到错误就及时停止
pathinfo
- 会把入口的一些的信息通过注释的形式输出。
devtoolModuleFilenameTemplate
- 帮助我们找到source-map真正的位置,从而帮助我们更好的调错
optimization
- 做一些优化和代码压缩
runtimeChunk
runtimeChunk: true // 会把runtime中的代码单独打包成一个文件
vue-cli
- 基于vue的脚手架工具