自动化构建
(一)npm scripts
npm允许在package.json
文件里面,使用scripts
字段定义脚本命令
{
// ...
"scripts": {
"build": "node build.js"
}
}
命令行下使用npm run
命令,就可以执行这段脚本。
$ npm run build
# 等同于执行
$ node build.js
$ npm run //查看所有脚本命令
之前项目弊端
-
网页加载速度慢,因为会发起很多的二次请求(html中引入css、js、image等)。
解决方案:合并文件、压缩、精灵图、将图片转成base64编码直接放入。
-
不能直接使用less、scss、ts等文件。
解决方案:先转成浏览器能够识别的文件css、js再引入。
-
要处理引入之间错综复杂的依赖关系。(比如:bootstrap前要引入jquery)。
解决方案:使用requireJS、webpack
-
es6及以上,旧版本浏览器不能很好支持。
解决方案:使用babel进行低版本浏览器兼容处理。
模块化打包
(一)webpack4
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler),依赖于node环境。
当 webpack 处理应用程序时,它会递归地构建一个依赖chunk关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
npm init //初始化包描述文件,输入包名称,以及其他信息,最终会生成package.json文件
npm i webpack webpack-cli -g //全局安装,以便于使用webpack命令
npm i webpack webpack-cli -dev //本地安装开发依赖
//运行打包项目指令
webpack //将打包结果输出
npx webpack-dev-server //在内存中编译打包,没有输出文件
1. 五个概念:
- 入口(entry):告知webpack从哪个文件开始打包。
- 输出(output):最终打包文件dundle存放位置。
- loader:让 webpack 能够去处理那些非 JavaScript/json 文件(webpack 自身只理解 JavaScript和json,不能处理css、img等。)。
- 插件(plugins):打包优化和压缩。
- 模式(model):
development
:能够让代码在本地调试运行。production
:代码优化后上线运行。
小技巧:node在当前目录中没有找打依赖包会到上级去找,所以可以把npm init 在多个目录最外层去install包,多个项目共用一份依赖包。
2. 配置
2.1 development
开发环境配置:
/**
* webpack.config.js webpack的配置文件
* 作用:指示webpack干什么(当运行webpack指令时,会加载里面的配置)
*
* 所有构建工具都是基于nodejs平台运行的,模块化默认采用commonjs格式。
*/
//resolve用来拼接绝对路径的方法(nodejs自带的path小工具)
const {resolve} = require('path');
//引入html插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//webpack配置
//入口起点
entry: './src/index.js',
//输出
output: {
//输出文件名
filename: 'js/built.js',
//输出路径:_dirname node.js的变量,代表当前目录的根目录(npm_learn/build)
path: resolve(__dirname, 'build')
},
//loader配置(打包样式资源css/less/img): 安装 使用
module: {
rules: [
//1.匹配css文件, npm i css-loader style-loader -D
{
test: /\.css$/,
//use数组中loader执行顺序:从右向左,从下向上 依次执行
use: [
//创建style标签,将js中的样式资源插入,添加到head中生效
'style-loader',
//将css文件变成commonjs模块加载js中,里面内容是样式字符串
'css-loader'
]
//不能使用outputPath: 'css'指定css输出目录,应为use规则:最终会将样式打包到js中
},
//2.匹配less文件, npm i css-loader style-loader less less-loader -D
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
//将less文件编译成css文件(下载less 和less-loader)
'less-loader'
]
//不能使用outputPath: 'css'指定css输出目录,应为use规则:最终会将样式打包到js中
},
//3.匹配图片资源, npm i url-loader file-loader -D
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
//图片小于8k ,就会被base64处理(服务器会一次性将图片资源转成base64数据一次性返回,减少请求压力,小体积图片可以使用)
limit: 8 * 1024,
//关闭url-loader的默认es6的模块解析(html-loader引入图片是commonjs会出现问题),使用commonjs解析
esModule: false
},
outputPath: 'imgs'
},
//4. 专门处理html文件中(包括模板html)的img图片(负责引入img,从而能被url-loader进行处理),npm i html-loader -D
{
test: /\.html$/,
loader: 'html-loader'
},
//5. 其他资源(除了html/css/js资源以外的资源)字体,图标等,npm i file-loader -D
{
exclude: /\.(css|js|html|less|json)$/,
loader: 'file-loader',
options: {
name: '[hash: 10].[ext]' //打包后的命名:hash值取10位,保留原先后缀名
},
outputPath: 'other'
}
]
},
//plugins配置(打包html资源): 安装 引入 使用
plugins: [
//1. 打包html资源 npm i html-webpack-plugin -D
new HtmlWebpackPlugin({
//不写template默认会创建一个空html,自动引入打包输出的所有资源(JS/CSS)
//复制 './src/index.html'文件,并自动引入打包输出的所有资源(JS/CSS)
template: './src/index.html'
}),
],
//模式
mode: 'development', //production
}
devServer:自动化(自动全部编译,自动打开浏览器,自动刷新浏览器)运行npx webpack-dev-server而不是webpack,否则看不到自动编译效果
const {resolve} = require('path');
module.exports = {
//devServer:开发服务器:用于自动化(自动编译,自动打开浏览器,自动刷新浏览器)
//特点:只会在内存中编译打包,不会有任何编译输出文件
//启动devServer指令为:npx webpack-dev-server(安装 npm i webpack-dev-server -D)
devServer: {
contentBase: resolve(__dirname, 'build'),
//启动gzip压缩
compress: true,
//端口号
port: 3000,
//自动打开默认浏览器
open: true
}
}
开发环境缺点:
-
css文件都统一打包到js中,会让js体积变大,加载js变慢会出现闪屏
-
代码未经过压缩
-
兼容性问题
2.2 production
生产环境配置,会自动压缩js:
webpack.config.js配置
const {resolve} = require('path');
//将css从js中抽离出来
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//压缩css
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
//html
const HtmlWebpackPlugin = require('html-webpack-plugin');
//定义nodejs环境变量,决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
//复用loader
const conmmonCssLoader = [
// 取代style-loader,作用:提取js中的css成单独文件
MiniCssExtractPlugin.loader,
'css-loader',
//css做兼容性处理,需要在package.json中定义browserslist
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')()
]
}
}
];
module.exports = {
entry: '.src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'built')
},
module: {
rules: [
{
test: /\.css$/,
use: [...conmmonCssLoader]
},
{
test: /\.less$/,
use: [...conmmonCssLoader,'less-loader']
},
//js语法规范检查,需在package.json中增加eslintConfig --> airbnb规则
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre', //优先执行
loader: 'eslint-loader',
//自动修复错误
options: {
fix: true
}
},
//js兼容性处理:es6转成es5兼容性会更好
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',//基本js兼容性处理,只能转换基本语法,像Promise不能转换
{
//按需加载(@babel/policy会将所有兼容性全引进去)
useBuiltIns: 'usage',
//按需加载使用corejs
corejs: {version:3},
//兼容chrome版本60+
targets: {
chrome: '60',
firefox: '50'
}
}
]
}
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false //关闭es6模块化,因为html-loader使用的commonjs模块化
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(js|css|less|html|jpg|png|gif)$/,
loader: 'file-loader',
options: {
outputPath:'other'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
//指定css输出文件
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
//压缩html
minify: {
collapseWhitespace: true, //去除空格
removeComments: true //去除注释
}
})
],
mode: 'production' //js自动压缩
}
package.json配置
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslientConfig": {
"extends": "airbnb-base",
// eslient不识别window、navigator等浏览器属性
"env": {
"browser": true
}
}
3. 优化(没有写笔记)
3.1 开发环境性能优化
-
优化打包构建速度(默认修改一个文件全部文件都会重新打包):基于devServe的HMR (Hot Module Replacement,一个模块发生变化,只会重新打包这一个模块)
/* 样式文件:可以使用HMR,style-loader默认内部会检测是否开启HMR js文件:默认不能使用HMR,会全部进行打包。需要在js文件添加指定代码 html文件:默认不能使用HMR,同时会导致不能热更新了。需要在入口文件加入html就行(一般入口文件不需变化,不用做HMR功能) */ const {resolve} = require('path'); module.exports = { entry: ['./src/js/index.js', './src/index.html'] devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, // 开启HMR功能 hot: true } }
// index.js import print from 'print'; // 开启HMR if (module.hot) { // 监听print.js文件的变化,以但变化,其他模块不会重新打包构建,会执行后面的回调 module.hot.accept('print.js', function() { print(); }) } // print.js function print() { console.log("js的HMR生效~~"); } export default pring;
-
优化代码调试 source-map(提供构建后代码报错,通过改该映射可以追踪源代码错误行数等信息)
const {resolve} = require('path'); module.exports = { entry: ['./src/js/index.js', './src/index.html'] devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, hot: true }, // 开启source-map devtool: 'source-map' }
3.2 生产环境性能优化
-
优化打包构建速度
- oneOf
/* oneOf的使用: */ const {resolve} = require('path'); module.exports = { module: { rules: [ { test: /\.js$/, exclude: /node_modules/, enforce: 'pre', loader: 'eslint-loader', }, // 以下loader当匹配一个后续就不再进行匹配,注意相同类型文件要将多余放到外部 oneOf: [ { test: /\.css$/, use: [...conmmonCssLoader] }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', } ] ] } }
- babel缓存(js文件进行babel-loader处理,比方说100个文件,只修改1个js,其他打包好的js是不会在重新打包)
/* babel缓存的使用:cacheDirectory: true,让第二次打包构建速度更快。 */ const {resolve} = require('path'); module.exports = { module: { rules: [ { test: /\.js$/, exclude: /node_modules/, enforce: 'pre', loader: 'eslint-loader', }, oneOf: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: {version:3}, targets: { chrome: '60', firefox: '50' } } ] }, // 开启babel缓存,第二次构建时,会读取之前的缓存 cacheDirectory: true } ] ] } }
-
多进程打包
/* thread-loader放在某个loader的后面,会对后面lodaer使用多进程处理 */ module.exports = { module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ // 开启多线程,进程开启大概为600ms,进程通信也有开销。耗时较长时再考虑使用。 { loader: 'thread-loader', options: { workers: 2 // 一般是最大核心数-1 } }, { loader: 'babel-loader', options: { presets: [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: {version:3}, targets: { chrome: '60', firefox: '50' } } ] }, cacheDirectory: true } ] } ] } }
-
externals:拒绝npm某个包被打包进去,需要再模板html中通过cdn引入。
/* index.html中手动引入:<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> */ module.exports = { module: {}, externals: { // 忽略被打包的库名 --npm 包名 jquery: 'jQuery' } }
-
优化代码运行性能
- 文件资源缓存:contenthash根据文件内容生成hash值,打包命名使用到该hash值,修改内容文件名称会变化,客户端检测到文件名称变化就会请求服务器资源,而不是使用本地缓存。
/* 文件资源缓存,让上线运行更好使用浏览器缓存: hash:每次webpack构建都会生成一个唯一的hash值。修改一个文件全部文件名称都会改变。 chunkhash:根据chunk生成的hash值。比如:入口文件引入多个css、js文件(入口文件和内部引用文件同属一个chunk),js和css的hash值一样。 contenthash:根据文件的内容生成hash值,不同文件hash值一定不同。 */ const {resolve} = require('path'); module.exports = { entry: '.src/js/index.js', output: { filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'built') }, module: {} }
- tree shaking:生产环境会自动开启(去除无用代码,较少代码体积,比如:只引入文件中一个方法,其他方法不会打包进去)
/* 前提:1.使用es模块化引入文件。2.webpack.config.js开启mode: production */ const {resolve} = require('path'); module.exports = { entry: '.src/js/index.js', output: { filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'built') }, module: {} }
// package.json设置哪些文件不进行tree shaking(“sideEffects”: false代表所有代码没有副作用,都可以进行tree shaking。问题:打包后生成的css文件去除) “sideEffects”: ["*.css"]
- code split:代码分割
/* optimization: 单入口:将第三方依赖包单独打包到一个js中 多入口:自动分析多入口chunk中,将第三方依赖单独打包一个chunk,相当于抽取成公共部分。 */ const {resolve} = require('path'); module.exports = { optimization: { splitChunks: { chunks: 'all' } }, // 解决:a 通过懒加载引入 b;当b内容改变时打包后的文件名会变,a中引入的b所以a内容也变了a也会重新打包。 // 生成runtime-[name].js管理打包后文件的名称,这样当a内容改变只会重新生成a和runtime-[name].js文件 runtimeChunk: { name: entrypoint => `runtime-${entrypoine.name}` } }
- 懒加载/预加载
// 通过js代码import动态导入语法,让某一个文件被单独打包成一个chunk // 通过/* webpackChunkName: 'test' */对打包文件命名 document.getElementById('btn').onclick = function() { // 懒加载:代码中真正使用到才会去 请求加载并执行; import(/* webpackChunkName: 'test' */'test').then().catch(); // 预加载prefetch: 等其他资源加载完毕,浏览器空闲了,会去预先请求加载,等到真正使用时立即执行,就无需等待网络的消耗; // (preload和prefetch的兼容性不太好) import(/* webpackChunkName: 'test', webpackPrefetch: true */'test').then().catch() }
-
PWA:借助ServiceWorker使其离线可访问,sw代码必须运行再服务器上
借助服务器 npm i service -g
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
/* optimization: 单入口:将第三方依赖包单独打包到一个js中 多入口:自动分析多入口chunk中,将第三方依赖单独打包一个chunk,相当于抽取成公共部分。 */ const {resolve} = require('path'); const WorkboxWebpackPlugin = require('work-webpack-plugin'); module.exports = { plugins: [ new WorkboxWebpackPlugin.GenerateSW({ // 1. 帮助servieWorker快速启动。2. 删除旧的serviceWorker clientsClain: true, skipWating: true }) ] }
// 入口文件index.js注册serivceWorker // 判断浏览器兼容serviceWOrker,等到浏览器加载完全部资源再注册 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/service.worker.js') .then(() => {console.log('sw注册成功');}) .catch(() => {console.log('sw注册失败');}) }) }