文章目录
- webpack的基本配置
- webpack如何配置多入口?
- 如何抽离压缩css文件
- 1、webpack 如何抽离公共代码和第三方代码?
- webpack 如何实现异步加载js?
- webpack 如何处理JSX 和vue 文件?
- module chunk bundle 的区别?
- webpack 的性能优化
- webpack 优化产出代码
- 使用production好处?
- 什么是Tree-Shaking (过滤掉没有调用到的代码)
- ES6 module 和Commonjs 的区别
- babel
- 前端为何要进行打包和构建?
- babel 和webpack 的区别
- 如何产出一个lib
- babel-polyfill 和babel-runtime 的区别
- webpack 如何实现懒加载
- 为何Proxy 不能被Polfyill
webpack的基本配置
- postcss-loader: 它负责进一步处理 CSS 文件,比如添加浏览器前缀,压缩 CSS 等。可以新建一个postcss.config.js文件单独定义postcss-loader配置
详细描述地址 - 处理图片
在开发环境可使用本地图片,线上环境可比较小的图片可进行base64转换一下
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
},
webpack如何配置多入口?
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
entry: {
index: path.join(srcPath, 'index.js'),
other: path.join(srcPath, 'other.js')
},
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
},
plugins: [
// new HtmlWebpackPlugin({
// template: path.join(srcPath, 'index.html'),
// filename: 'index.html'
// })
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index'] // 只引用 index.js
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other'] // 只引用 other.js
})
]
- new CleanWebpackPlugin(), 每次打包会默认清空 output.path (dist)文件夹,在plugin中写
如何抽离压缩css文件
- 生产环境下使用mini-css-extract-plugin插件,可以抽离css
- 将抽离出来的css文件进行压缩使用terser-webpack-plugin(将css中的注释抽离)、optimize-css-assets-webpack-plugin(压缩css)
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module export = {
module: {
rules: [
// 抽离 css
{
test: /\.css$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'postcss-loader'
]
},
// 抽离 less --> css
{
test: /\.less$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
},
plugins: [
// 抽离 css 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
optimization: {
// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
}
}
1、webpack 如何抽离公共代码和第三方代码?
为什么要抽离公共代码和第三方代码?
- 因为当多个组件引入同一个组件的时候,打包的时候都会再次去打包引入的那个组件,这就造成了浪费。同理,当多个组件引入第三方代码,那每次打包有多少个页面引入第三方过就会打包多少次,如果是很大的第三方代码就会造成页面加载慢。所以要抽离公共代码和第三方代码;
直接上代码:
在你的webpack.prod.js中使用splitChunks 分隔,
optimization: {
// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
// 分割代码块
splitChunks: {
chunks: 'all',
/**
* initial 入口 chunk,对于异步导入的文件不处理
async 异步 chunk,只对异步导入的文件处理
all 全部 chunk
*/
// 缓存分组
cacheGroups: {
// 第三方模块
vendor: {
name: 'vendor', // chunk 名称
priority: 1, // 权限更高,优先抽离,重要!!!
test: /node_modules/,
minSize: 0, // 大小限制
minChunks: 1 // 最少复用过几次
},
// 公共的模块
common: {
name: 'common', // chunk 名称
priority: 0, // 优先级
minSize: 0, // 公共模块的大小限制
minChunks: 2 // 公共模块最少复用过几次,就会将那个组件分配到common模块中缓存起来
}
}
}
}
webaopck.common.js
plugins: [
// new HtmlWebpackPlugin({
// template: path.join(srcPath, 'index.html'),
// filename: 'index.html'
// })
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index', 'vendor', 'common'] // 要考虑代码分割
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other', 'common'] // 考虑代码分割
})
]
webpack 如何实现异步加载js?
- 使用import 包裹你的异步js文件,这是webpack里自带的,打包的时候默认将异步js文件单独打包
webpack 如何处理JSX 和vue 文件?
JSX:
- 可以去bable.js 里去查找我们的react ,安装 npm install --save-dev @babel/preset-react
- 安装完成之后我们在.babelrc 文件这么配置
{
"presets": ["@babel/preset-react"]
}
webpack 中的loader就是用bable-loader 就好了
module:{
rules:[
{
test: /\.js$/,
loader: ['bable-loader'],
include: srcPath // src 目录下的所有文件
},
]
}
vue
- 安装 vue-loader
然后再装载器中写上我们的规则,.vue文件使用的vue-loader装载器
module:{
rules:[
{
test: /\.vue$/,
loader: ['vue-loader'],
include: srcPath
},
]
}
module chunk bundle 的区别?
- module 各个源码文件,webpack 中一切皆模块,src 下的js文件css文件等都是module
- chunk 多模块合并成的,如entry impot() splitChunk
- bundle 最终的输出文件,例如.js .css .png
webpack 的性能优化
-
优化打包构建速度
- 优化 babel-loader: 在 babel-loader后加cacheDirectory,加上这个之后,只要es6 代码没有改变第二次编译时就不会重新编译;第二个是明确编译范围,使用include ,一般在src下的文件。用于开发环境就可以了
- IgnorePlugin : 避免引入无用模块,直接不引入,代码中没有
例如: moment 是时间的插件,安装之后有很多的语言文件,使用IgnorePlugin 就可做到只引入中文。代码如下
plugins:[
new webpack.IgnorePlugin(/\.\/local/,/moment/) // 打包的时候忽略local文件的内容
]
如果想引入中文就要手动引入中文包,代码如下
import moment from 'moment'
import 'moment/local/zh-cn'
moment.loacl('zh-cn')
moment.format('YYYY-MM')
- noParse 避免重复打包,就是我们代码中引入了那种xx.main.js 的文件,这些都是别人已经打包处理过了,所以我们不需要再打包一遍,就可使用noParse。引入不打包
- happyPack 多进程打包,可在开发环境和生产环境下使用
- js单进程,开启多进程打包
- 提高构建速度,(特别是多核CPU)
const { srcPath, distPath } = require('./paths')
const happyPack = require('happypack')
module:{
rules:[
// js 之前是bable-loader,使用happypack之后就得改成如下
{
test:/\.js$/,
// 把对js文件的处理转交给id为babel的HappPack 实例处理
use:['happypack/loader?id=babel'],
include:srcPath
// exclude:/node_modules/
}
]
},
plugins:[
// happypack 开启多进程打包
new HappyPack({
// 用唯一的标识付id来代表当前的HappyPack 是用来处理一类特定的文件
id:'babel',
// 如何处理.js 文件,用法和loader 配置中国的一样
loaders:['babel-loader?cacheDirectory']
})
]
- ParallelUglifyPlugin 多进程压缩js,只能放在生产环境下
- webpack 内置的Uglify 工具压缩JS
- JS单线程,开启多进程压缩更快
- 和happyPack 同理
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins:[
new ParallelUglifyPlugin({
// 传递给UGLIFYjs 的参数
// 还是使用UglifuJS压缩,只不过帮厨开启了多线程
uglifyJS:{
output:{
beautify:false,// 最紧凑的输出
comments:false,// 删除所有的注释
},
compress:{
deop_console:true, // 删除所有的console语句,可以兼容ie浏览器
collapse_vars:true,// 内嵌定义了但是只用到一次的变量
reduce_vars:true // 提取出出现多次但是没有定义成变量去引用的静态值
}
}
})
]
- 关于开启多进程
- 项目较大,打包较慢,开启多进程能提高速度
- 但是如果项目较小,打包很快,开启多进程会降低速度(进程开销)
- 按需使用
- 我们代码改完之后,浏览器就自动刷新,但是一般不会去自己配置这个,因为你只要配置了devServer就自带的有代码更新自动刷新的功能
- 如何配置热更新
- 自动刷新:整个网页全部刷新,速度较慢,状态会丢失,
- 热更新:新代码生效,网页不刷新,状态不丢失,路由不丢失,对于css 直接开启devServer 即可,但是js文件就不行了,只能用于开发环境,如果你要去生产环境的话,就得把那些代码删掉
- 需要你额外去处理那些模块需要热更新然后调用一个回调,就成本来挺大,如果你的项目不是很大,网页刷新对你开发没啥影响也可以不用开启热更新
webapck 配置代码如下:
cost HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin')
entry:{
// 不用hot是这样子的 index:path.join(srcPath,'index.js')
// 使用热更新之后要这么写
index:[
'webpack-dev-server/client?http://loaclhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath,'index.js')
]
},
olugins:[
new HotModuleReplacementPlugin()
],
devServer:{
// 此处省略很多配置
hot:true
}
** 需要热更新的JS模块**
import {sum} from './math'
// 开启热更新之后的代码
if(module.hot){
module.hot.accept(['./math'],()=>{
const sumRes = sum(10,20)
})
}
- DllPlugin 动态链接库
- 前端框架如vue react 体积大,构建慢
- 比较稳定,不常升级版本
- 同一个版本只构建一次即可,不用每次都重新构建
- webpack 以内置DllPlugin 支持,不需要我们自己去下载
- DllPlugin插件 - 打包出dll文件(我们可以先把vue或react 进行预打包一遍)
- DllReferencePlugin 插件 - 使用dll文件
- 配置之后对代码的引入没有任何修改
(1): 单独对需要使用DllPlugin的框架创建一个文件webpack.dll.js
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
(2):然后在package.json中配置运行命令
"scripts": {
"dev": "webpack serve --config build/webpack.dev.js",
"dll": "webpack --config build/webpack.dll.js"
},
(3): 运行命令,npm run dll 会打包出两个文件:
react.dll.js, react.manifest.json
(4):然后在index.html中引入react.dll.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="./react.dll.js"></script>
</body>
</html>
(5):在webpack.dev.js 文件中引入DllReferencePlugin,并使用
// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = merge(webpackCommonConf, {
mode: 'development',
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
},
]
},
plugins: [
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('development')
}),
// 第三,告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
}),
],
})
(6)、就可以去引入react 了,构建块
webpack 优化产出代码
* 打包体积更小,加载就快
* 合理分包,不重复加载(比如多个入口很重复的代码,他能合理的分包,把重复的逻辑分出来,就可以相互引用,而不是像复制黏贴一样的去打包,这会让我们的代码加载的更快,体积更小,逻辑更合理)
* 执行速度更快,内存使用更少
(1)、小图片base64 编码
(2)、bundle 加hash
(3)、懒加载
(4)、提取公共代码
(5)、IngorePlugin: 避免引入无用模块,直接不引入,代码中没有
(6)、使用cdn加速
第一步,在webpack中配置cnd的地址,publicPath 修改多有静态文件url 的前缀
第二步,把打包之后的文件上传到cdn去,比如css文件和js文件
或者一些图片
(7)、使用production ,自动压缩代码,在生产环境下使用
- 自动开启压缩代码
- vue React 在produnction 环境下的话会自动删掉调试代码(如开发环境的warning ),让体积更小一些
- 启用tree-shaking (过滤掉无用的代码)
(8)、Scope Hosting
- 代码体积更小,压缩之后的代码会将两个文件里的代码合并成一个函数,没有使用这个的话,会将每个文件创建一个函数,就会出现代码很多很乱
- 创建函数作用域更少
- 代码可读性更好
**使用scope hosting **
使用production好处?
- 自动压缩代码
- vue react等会自动删掉调试代码
- 启动tree-shaking
什么是Tree-Shaking (过滤掉没有调用到的代码)
webpack4有这个功能,很弱,只支持当前js文件中无用的代码;webpack5加强了这个功能。但是只会在生产模式下才会过滤,开发模式都会打包。
注意: ES6 module 才能让tree-shaking生效,使用commonjs 就不行
ES6 module 和Commonjs 的区别
- es6 Module 静态引入,编译时引入
- Commonjs 是动态引入,执行时引入
- 只有es6 module 才能静态分析,实现tree-shaking
babel
- 前端开发环境必备工具
- 同webpack ,需要了解基本的配置和使用
1、babel 环境搭建和基本配置
babel解决 语法层面的问题。用于将ES6+的高级语法转为ES5。而要解决 API层面的问题,如Map、Proxy、Reflect、Symbol、Promise等api,是不能转换的,所以需要polyfill 来解决。常见的有 babel-polyfill、 babel-runtime 和 babel-plugin-transform-runtime。
2、babel -polyfill
- 优点: 一次性解决所有兼容性问题,而且是全局的,浏览器的console也可以使用
- 缺点:
- 一次性引入了ES6+的所有polyfill, 打包后的js文件体积会偏大
- 对于现代的浏览器,有些不需要polyfill,造成流量浪费
- 污染了全局对象
- 适合框架或库的开发
3、babel-runtime
- 它将开发者依赖的全局内置对象等,抽取成单独的模块,并通过模块导入的方式引入,避免了对全局作用域的修改(污染)。
- 缺点: 每个模块内单独引用和定义polyfill函数,造成了重复定义,使代码产生冗余
4、babel-plugin-transform-runtime
- 优点:
- 无全局污染
- 依赖统一按需引入(polyfill是各个模块共享的), 无重复引入, 无多余引入
- 适合用来编写lib(第三方库)类型的代码
- 缺点:
- 被polyfill的对象是临时构造并被import/require的,并不是真正挂载到全局
- 由于不是全局生效, 对于实例化对象的方法,如[].include(x), 依赖于Array.prototype.include仍无法使用
前端为何要进行打包和构建?
代码层面讲:
- 体积更小(tree-shaking,压缩,合并),加载更快
- 编译高级语言或语法(TS、ES6+,模块化、scss等)
- 兼容性和错误提示(Polyfill、postcss 、eslint)
团队效率方面: - 统一、高效的开发环境
- 统一的构建流程和产出标准
- 集成公司构建规范(提测、上线等)
loader 和plugin 的区别
- loader模块转换器,如less->css,es6->es5
- plugin扩展插件,如 HtmlWebpackPlugin ,html 打包进dist文件夹里去
常见的loder 和 plugin 的有哪些?
babel 和webpack 的区别
- babel 是js新语法编译工具,不关心模块化
- webpack 是打包构建工具,十多个loader plugin 的集合
如何产出一个lib
babel-polyfill 和babel-runtime 的区别
- babel-polyfill 会污染全局
- babel-runtime 不会污染全局
- 产出第三方的lib 要用babel-runtime
webpack 如何实现懒加载
- import
- 结合vue react 异步组件
- 结合vue-router react-router 异步加载路由
为何Proxy 不能被Polfyill
- 如class 可以用function 模拟
- 如promise 可以用callback 来模拟
- 但是Proxy 的功能用Object.defineProperty 无法模拟