- ES6 模块化
- ES6 语法
- 压缩、整合代码,加快网页加载速度
安装 webpack
npm install webpack webpack-cli
根目录下 webpack.config.js 文件
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin') //解析HTML插件
module.exports = {
mode: 'development', // production 生产环境
entry: path.join(__dirname, 'src', 'index.js'), // 文件入口
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
plugins: [
new HtmlWebpackplugin({
template: path.join(__dirname, 'src', 'index.html'),
filename: 'index.html'
})
],
devServer: {
port: 8818,
contentBase: path.join(__dirname, 'dist'),
}
}
package.json 中
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server" // 本地服务
},
将 es6 语法转为 es5
const name = ''
const obj = {
name: '11'
}
export {
name,
obj
}
-----------------------
export function add(){
console.log('aa')
}
export const name = "张胜男"
import {name} from './a'
export default仅有一个,且导入时不加{}。使用 export default,不需要知道所需加载模块的变量名。
- vue-cli2 配置
修改config目录下的index.js文件
module.exports = {
bulid: {
productionSourceMap: false
}
}
- vue-cli3和vue-cli4 配置
修改根目录下的vue.config.js文件
module.exports = {
productionSourceMap: false
}
- 前端代码为何要构建和打包
- moudule chunk bundle 分别是什么,区别
- loader 与 plugin 区别
- webpack 懒加载
- webpack 常见性能优化
- babel-runtime 和 babel-polyfill 区别
-
拆分配置和 merge
webpack 分为公共配置,开发环境配置,生产环境配置
webpack-merge 可以在公共配置基础上配置别的环境
const {smart} = require('webpack-merge') module.exports = smart(webpackCommonConf,{ ... })
-
loader 的执行顺序是 从后往前
{ test: /\.css$/, loader: ['style-loader','css-loader','postcss-loader'] // postcss-loader 用来做浏览器兼容性 }
-
处理图片
{ 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' } } },
-
entry: { index: path.join(srcPath, 'index.js'), other: path.join(srcPath, 'other.js') }, output: { 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 }) ]
-
开发环境可以用 style-loader 将 css 放到 style 中
// 抽离 css { test: /\.css$/, loader: [ MiniCssExtractPlugin.loader, //不再用 style-loader 'css-loader', 'postcss-loader' ] }, plugins: [ new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹 new webpack.DefinePlugin({ // window.ENV = 'production' ENV: JSON.stringify('production') }), // 抽离 css 文件 new MiniCssExtractPlugin({ filename: 'css/main.[contentHash:8].css' }) ], optimization: { // 压缩 css minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})], }
-
公共代码,例如多入口引用了同一个库。
第三方代码,例如改变了业务代码,但是很多库的代码其实没有变。但是又要重新打包,contenthash值改变,于是重新加载的内容还包括了没有变化的库。
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 // 公共模块最少复用过几次 } } } } // 每个入口包含的 chunk plugins: [ new HtmlWebpackPlugin({ template: path.join(srcPath, 'index.html'), filename: 'index.html', chunks: ['index', 'vendor', 'common'] // 要考虑代码分割 }), new HtmlWebpackPlugin({ template: path.join(srcPath, 'other.html'), filename: 'other.html', chunks: ['other', 'common'] // 考虑代码分割 }) ]
-
webpack 会对异步的 js 单独打包
setTimeOut(()=>{ import(./1.js).then(res=>{ console.log(res.default.message) }) }),500)
-
安装 vue-loader
{ test: /\.vue$/, loader: ['vue-loader'], include: srcPath },
- module - 各个源码文件 可以引用的都是模块,webpack 中一切皆模块
- chunk - 多个模块合并成的一坨 例如 entry splitChunk import
- bundle - 最终的输出文件 一般一个 chunk 对应一个 bundle
一般不用于生产环境
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory'], //开启缓存
include: path.resolve(__dirname, 'src'), //明确缓存范围
// 排除范围可以用 exclude 用法和 include 一样
}
例如 moment 主要对时间格式化,支持多国语言,但是打包时会将所有语言都打包进去。
new webpack.IgnorePlugin(/\.\/locale/, /moment/) // 忽略locale文件夹
// 要用到时,可以手动引入
import 'moment/locale/zh-cn'
可用于生产环境
当 webpack 解析引用时会解析它的依赖,但是很多时候第三方库不用去考虑依赖
module:{
noParse:/jquery/,//不去解析jquery中的依赖库
rules:[
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:[
'@babel/preset-env',
'@babel/preset-react'
]
}
}
}
]
},
多进程打包,js 是单线程,所以可以使用多进程打包提升构建速度
// 首先安装 happypack
const HappyPack = require('happypack')
module: {
rules: [
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
},
]
}
plugins:[
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
]
多进程压缩 JS。和 happypack 同理。一般生产环境下使用。
webpack 内置 Uglify 工具压缩 JS,但是是单线程运行。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins:[
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
]
- 项目较大时,开启多进程能够提高速度
- 但是项目较小时,开启多进程的开销反而会降低速度
一般使用 devserver 就会自动开启。不用于生产环境。
不可用于生产环境。
- 自动刷新:整个网页全部刷新,速度较慢
- 自动刷新:整个网页全部刷新,状态会丢失
- 热更新:新代码生效,网页不刷新,状态不丢失
需要在代码中配置哪些模块需要热更新
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
plugins: [
new HotModuleReplacementPlugin()
],
devServer: {
hot: true,
}
if(module.hot){
module.hot.accept([./js], ()=>{
console.log('hot')
})
}
不用于生产环境。
动态链接库插件,webpack 内置支持
DllPlugin - 打包出 dll 文件
DllReferencePlugin - 使用 dll 文件
- 前端框架体积大,构建较慢
- 但是框架稳定,不常升级版本
- 所以同一个版本只构建一次即可,不用每次重新构建
// 1、首先新建一个 js 文件来配置 DllPlugin
// 2、运行 npm run dll 来生成两个文件:dll 文件和描述这个 dll 文件的文件。
// 3、在生产环境中引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
// 4、不再转换 node_modules 的代码
exclude: /node_modules/
// 5、告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
}),
- 打包代码体积更小
- 合理分包,不重复加载
- 速度更快,内存使用更少
-
小图片使用 base64 编码 - 不再网络请求
-
bundle 加 hash
-
懒加载
-
提取公共代码
-
IgnorePlugin
-
CDN 加速
先设置 CDN 的 url,再把静态文件放到 CDN 上
-
使用 production
-
Scope Hosting
-
自动开启代码压缩 - webpack4 之后
-
Vue React 等会自动删掉调试代码
-
启动 Tree-Shaking - 自动删掉没有被调用的函数
ES6 Module 才能让 Tree-Shaking 生效, Commonjs 不行
- ES6 Module 静态引入,编译时引入
- Commonjs 动态引入,执行时引入
- 只有 ES6 Module 才能静态分析
let apiList = require('../config/api.js')
if(isDev){
apiList = require('../config/api_dev.js')
}
import apiList from '../config/api.js'
if(isDev){
import apiList from '../config/api_dev.js' // 报错
}
打包时把多个文件打包成一个函数
- 代码体积更小
- 创建的函数作用域更少
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
module.exports = {
resolve: {
mainFields: ['jsnext:main','browser', 'main']
},
plugins: [
new ModuleConcatenationPlugin()
]
}
优先级相对于 webpack 更低。
.babelrc
{
"presets":[
// 预设,是一堆常用 plugin 的集合,能满足常用语法的解析
["@babel/preset-env"]
],
"plugins":[
]
}
-
polyfill 本质是补丁,用来适配一些新的 api
-
core-js 和 regenerater
core-js 是一个标准的库,基本集成了所有的 polyfill
regenerater 是一个 js 的 generater 的 polyfill
-
babel-polyfill 是上面两个的结合
babel 7.4 之后弃用 babel-polyfill
推荐直接使用 core-js 和 regenerater
import '@babel/polyfill'
// 新 api
Promise.resolve(100).then(data => data)
[1,2,3,4].includes(4)
// 虽然是新 api 但是语法符合 ES5,所以 babel 不会去转化它
// 可能浏览器不支持这些新的 api,所以需要 babel-polyfill 来对它进行一个兼容
// babel 不解析模块化
- babel-polyfill 文件很大
- 一般不需要所有引入
.babelrc
{
"presets":[
// 预设,是一堆常用 plugin 的集合,能满足常用语法的解析
[
"@babel/preset-env"
{
"useBuiltIns": "usage", // 按需引入
"corejs": 3 // corejs 版本
}
]
],
"plugins":[
]
}
污染全局
window.Promise = function(){}
Array.prototype.includes = function(){}
//会直接修改全局的 Promise 和 includes
如果开发一个系统则可以,但是要做第三方库的话就要注意
解决全局污染的问题
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 3,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
- 体积更小,加载更快(Tree shaking、压缩、合并)
- 可以编译高级语言或语法(TS、ES6)
- 兼容性和错误检查
- 统一、高效的开发环境
- 统一的构建流程和产出标准
- 集成公司的构建规范
- loader 是一个转换器
- plugin 是扩展
- babel 是新语法编译工具,不关系模块
- webpack 是打包构建工具,是多个 loader 和 plugin 的集合
ouput:{
// lib 文件名
filename: 'lodash.js',
// 输出目录
path: distPath,
// lib 全局变量名
library: 'lodash',
}
-
Promise 可以用 callback 来模拟
-
class 可以用 function 来模拟
-
Proxy 的功能用 defineProperty 模拟不了