经过如何利用webpack来提高前端开发效率(一)的学习,咱们已经可以经过webpack
的loader
和piugin
机制来处理各类文件资源。细心的小伙伴们发现了缺乏了对字体文件和HTML
中<img>
标签的资源处理,那让咱们先来解决这个问题。css
接上篇文章,咱们的目录结构,如图所示: html
webpack.config.js
// webpack.config.js
// 新增对字体的loader
{
test: /\.(eot|woff|woff2|ttf)$/,
use: [{
loader: 'url-loader',
options: {
name: '[name].[hash:7].[ext]',
limit: 8192,
outputPath: 'font', // 打包到 dist/font 目录下
}
}]
},
复制代码
如何咱们从网上随意下载了一种字体,放置于src
文件夹下,并修改src/index.html
前端
<!-- src/index.html -->
<!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>my-webpack</title>
</head>
<body>
<h1>webpack大法好!!前端大法好!!</h1>
</body>
</html>
复制代码
在index.scss
中引入字体node
/* src/index.scss */
/* 添加如下样式 */
@font-face {
font-family: 'myFont';
src: url('./font/ZaoZiGongFangQiaoPinTi-2.ttf');
}
h1 {
font-family: 'myFont';
}
复制代码
在此以前,每次从新打包都要删除dist
文件夹,实在是麻烦,如今咱们能够借助clean-webpack-plugin
,它可以在每次打包时删除指定的文件夹,咱们在命令行执行 npm i clean-webpack-plugin -D
webpack
修改webpack.config.js
git
// webpack.config.js
// 新增如下引入
const CleanWebpackPlugin = require('clean-webpack-plugin');
// 新增如下插件
plugins: [
new CleanWebpackPlugin(['dist']) // 在最新的v2版本中,若是默认删除dist文件夹,只需new CleanWebpackPlugin()
],
复制代码
随后在命令行执行npm run build
,咱们的dist
文件夹会被自动删除,并输出如下结果,能够看到咱们虽然成功打包了字体文件,但字体文件是在太大,连webpack
都发出了警告[big]
。github
CDN
加速font-spider
对字体进行压缩咱们实践一下第三种方案,也是我推荐的方案 在命令行依次执行web
npm i font-spider -D
font-spider ./dist/index.html
复制代码
能够看到将近4MB的字体文件体积瞬间压缩至不足6KB!!!而页面效果和以前如出一辙。 npm
HTML
文档中
<img>标签的引入问题
,咱们须要借助
html-loader
,它能将
HTML
文档中
img.src
解析成
require
,从而实现引入图片,话很少说,咱们直接看效果。在命令行执行
npm i html-loader -D
修改如下文件json
// webpack.config.js
// 新增对html的loader
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src'] // img表明解析标签,src表明要解析的值,以key:value形式存在于attrs数组中
}
}
}
复制代码
<!-- src/index.html -->
<body>
+ <img src="./leaf.png" alt="">
</body>
复制代码
在命令行执行npm run build
,查看dist/index.html
,看来已经成功啦
设想若是咱们的入口文件很大(包含了全部的业务逻辑代码),就会形成首屏加载变慢,用户体验感降低。 这里咱们从两个方面解决:
src
目录下新增dynamic.js
// dynamic.js
export default () => {
console.log('Im dynamically loaded.');
}
复制代码
修改如下文件
<!-- src/index.html -->
<body>
+ <button id="btn">点击我,动态加载dynamic.js</button>
</body>
复制代码
// src/index.js
// 新增如下内容
const btn = document.getElementById('btn');
// 点击按钮,动态加载dynamic.js
btn.onclick = () => {
import(/* webpackChunkName: "dynamic" */ './dynamic.js').then(function (module) {
const fn = module.default;
fn();
})
}
复制代码
执行npm run build
,能够看到
/* webpackChunkName: "dynamic" */
,则是
ChunkName
为
"dynamic"
是必要的,不然打包完成会是以自动分配的、可读性不好的
id
命名的
JS
文件。且没有
Chunk Names
标识。
如今咱们打开dist/index.html
,此时
dynamic.js
回头看咱们的webpack.config.js
,不知不觉就写了这么多代码,鉴于咱们在开发实际项目时,是开发和生产两套工做模式,各司其职,咱们不如作个了断,分离配置。
命令行执行npm i webpack-merge cross-env -D
webpack-merge
能够合并webpack配置项,cross-env
能够设置及使用环境变量。
新增webpack.base.js
,提供基本的webpack loader plugin
配置
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
const devMode = process.env.NODE_ENV !== 'production';
// 在node中,有全局变量process表示的是当前的node进程。
// process.env包含着关于系统环境的信息。
// NODE_ENV是用户一个自定义的变量,在webpack中它的用途是来判断当前是生产环境或开发环境。
// 咱们能够经过 cross-env 将 NODE_ENV=development 写入 npm run dev的指令中,从而注入NODE_ENV变量。
module.exports = {
entry: {
index: pathResolve('js/index.js')
},
output: {
path: pathResolve('dist'),
},
module: {
rules: [
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src']
},
},
},
{
test: /\.(eot|woff|woff2|ttf)$/,
use: [{
loader: 'url-loader',
options: {
name: '[name].[hash:7].[ext]',
limit: 8192,
outputPath: 'font',
},
}],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? 'style-loader' : { // 若是处于开发模式,则无需再外链CSS,直接插入到<style>标签中
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash:7].[ext]',
outputPath: 'img',
},
}],
},
],
},
plugins: [
new htmlWebpackPlugin({
minify: {
collapseWhitespace: true, // 移除空格
removeAttributeQuotes: true, // 移除引号
removeComments: true // 移除注释
},
filename: pathResolve('dist/index.html'),
template: pathResolve('src/index.html'),
})
]
};
复制代码
新增webpack.dev.js
,服务于开发模式下
const path = require('path');
const webpack = require('webpack');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
module.exports = smart(base, {
mode: 'development',
output: {
filename: 'js/[name].[hash:7].js'
},
devServer: {
contentBase: pathResolve('dist'),
port: '8080',
inline: true,
historyApiFallback: true,
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
]
})
复制代码
新增webpack.prod.js
,服务于生产模式下
const path = require('path');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
module.exports = smart(base, {
mode: 'production',
devtool: 'source-map', // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度,适用于打包后的代码查错
output: {
filename: 'js/[name].[chunkhash:7].js',
chunkFilename: 'js/[name].[chunkhash:7].js',
},
plugins: [
new CleanWebpackPlugin(['dist']),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:7].css',
}),
],
});
复制代码
相应的,package.json
也须要修改
// 新增如下两条命令
// cross-env 决定运行环境 --config 决定运行哪一个配置文件
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.prod.js "
复制代码
缓存在前端的地位毋庸置疑,正确的利用缓存就能极大地提升应用的加载速度和性能。 webpack
利用了hash
值做为文件名的组成部分,能有效利用缓存。当修改文件,从新打包时,hash值就会改变,致使缓存失效,HTTP请求从新拉取资源。
而webpack
有三种hash处理策略,分别是:
属于项目工程级别的,即每次修改任何一个文件,全部文件名的hash
值都将改变。因此一旦修改了任何一个文件,整个项目的文件缓存都将失效。如将整个项目的filename
的命名策略改成name.[hash:7]
(:7的意思是从完整hash值中截取前七位),咱们能够看到,打包后的文件hash值是同样的,因此对于没有改变的模块而言,hash也被更新了,致使缓存失效了。
chunkhash
根据不一样的入口文件(Entry
)进行依赖文件解析、构建对应的chunk
,生成对应的哈希值。如将整个项目filename
的命名策略改成name.[chunkhash:7]
,咱们能够看到Chunk Names
为"index"
的文件hash值一致,而不一样chunk
的hash
值不一样。这也就避免了修改某个文件,整个工程hash值都将改变的状况。
但问题随之而来,index.scss
是做为模块导入到index.js
中的,其chunkhash
值是一致的,只要其中之一改变,与其关联的文件chunkhash
值也会改变。这时候就要用到contenthash
,它是根据文件的内容计算,该文件的内容改变了,contenthash值才会改变。咱们将css文件的命名策略改成name.[contenthash:7]
,并修改src/index.js
,不改动其余文件,再次打包,发现:
字面意思理解为从一棵树上把叶子摇晃下来,这样数的重量就减轻了,类比程序,就如同从咱们的应用上删除没用的代码,从而减小体积。借于ES6
的模块引入是静态分析的,故而webpack
能够在编译时正确判断到底加载了什么代码,即没有被引用的模块不会被打包进来,减小咱们的包大小,缩小应用的加载时间,呈现给用户更佳的体验。那么怎么使用呢?
新建src/utils.js
// src/utils.js
const square = (num) => num ** 2;
const cube = num => num * num * num;
// 导出了两个方法
export {
square,
cube
}
复制代码
新建src/shake.js
// src/shake.js
import { cube } from './utils.js';
// 只使用了cube方法
console.log('cube(3) is' + cube(3));
复制代码
在webpack.base.js
中新增入口文件shake.js
entry: {
+ shake: pathResolve('src/shake.js')
},
复制代码
命令行执行npm run build
,查看打包后的shake.js
,并无发现square
方法没有被打包进来,说明tree-shaking
起做用了。 而这一切都是webpack
在production
环境下自动为咱们实现的。
字面意思为拆分代码块,默认状况下它将只会影响按需加载的代码块,由于改变初始化的代码块将会影响HTML
中运行项目须要包含的script
标签。还记得咱们在src/index.js
中动态引入了src/dynamic.js
吗,最终dynamic.js
被独立打包,就是归功于splitChunks
。
在实际生产中,咱们常常会引入第三方库(JQuery
,Lodash
),每每这些第三方库体积高达几十KB掺杂在业务代码中,而且不会像业务代码同样常常更新,这时候咱们就须要将他们拆分出来,既能保持第三方库持久缓存,又能缩减业务代码的体积。
修改webpack.prod.js
// 在module.exports中新增以下内容
optimization: {
runtimeChunk: {
name: 'manifest', // 被注入了webpackJsonp的定义及异步加载相关的定义,单独打包模块信息清单,利于缓存
},
splitChunks: {
cacheGroups: { // 缓存组,默认将全部来源于node_modules的模块分配到叫作'venders'的缓存组,全部引用超过两次的模块分配到'default'缓存组.
vendor: {
chunks: "all", // all, async, initial 三选一, 插件做用的chunks范围,推荐all
test: /[\\/]node_modules[\\/]/, // 缓存组所选择的的模块范围
name: "vendor", // Chunk Names及打包出来的文件名
minChunks: 1, // 引用次数>=1
maxInitialRequests: 5, // 页面初始化时加载代码块的请求数量应该<=5
minSize: 0, // 代码块的最小尺寸
priority: 100, // 缓存优先级权重
},
}
}
},
复制代码
命令行执行npm i lodash -S
修改src/index.js
// 新增如下内容
import _ from 'lodash';
复制代码
执行npm run build
,能够看到优化前lodash
被打包进index.js
,优化后lodash
被打包进vendor.js
。
每每在CSS代码中,存在不少咱们没有用到的样式,它们是冗余的,咱们须要将它们剔除,并压缩剩余的CSS样式,以减小CSS文件体积。
在命令行执行npm i glob optimize-css-assets-webpack-plugin purifycss-webpack purify-css -D
修改webpack.prod.js
// 新增如下引入
const glob = require('glob'); // 匹配所需文件
const PurifyCssWebpack = require('purifycss-webpack'); // 去除冗余CSS
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); // 压缩CSS
// 新增如下插件
new PurifyCssWebpack({
paths: glob.sync(pathResolve('src/*.html')) // 同步扫描全部html文件中所引用的css,并去除冗余样式
})
// 新增如下优化
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({}) // 压缩CSS
]
}
复制代码
执行npm run build
CSS
,并压缩至一行。接下来,咱们须要压缩
JS
代码。 因为咱们使用的是
uglifyjs-webpack-plugin
,它须要ES6的支持,因此咱们先让工程支持ES6的语法。 Babel 是一个 JavaScript 编译器。它能把下一代 JavaScript 语法转译成ES5,以适配多种运行环境。
@babel/core
提供了babel的转译API,如babel.transform
等,用于对代码进行转译。像webpack
的babel-loader
就是调用这些API来完成转译过程的。
@babel/preset-env
能够根据配置的目标浏览器或者运行环境来自动将ES2015+
的代码转换为ES5
。
先在命令行执行npm i @babel/core @babel/preset-env babel-loader @babel/plugin-syntax-dynamic-import -D
新建.babelrc
文件
{
"presets": [ // 配置预设环境
["@babel/preset-env", {
"modules": false
}]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import" // 处理src/index.js中动态加载
]
}
复制代码
修改webpack.base.js
// 新增js的解析规则
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/
},
复制代码
而后命令行执行npm i uglifyjs-webpack-plugin -D
修改webpack.prod.js
// 新增如下引入
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
// 新增如下优化
optimization: {
minimizer: [
+ new UglifyJsPlugin({ // 压缩JS
cache: true,
parallel: true,
sourceMap: true
})
]
}
复制代码
执行npm run build
,能够看到打包的文件体积大大减小,大功告成,JS
也被压缩了。
以index.html
为例,咱们能够打开Chrome的开发者工具,选择More tools,点击Coverage面板,能够看到JS、CSS等文件的使用率,配合咱们定制的webpack配置进行极致优化。
有时候,咱们须要同时构建多个页面,借助html-webpack-plugin
,只需在plugins
中添加新页面的配置项。
新增src/main.html
<!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>main page</title>
</head>
<body>
<h1>I am Main Page</h1>
</body>
</html>
复制代码
修改webpack.base.js
// 修改如下内容
plugins: [
new htmlWebpackPlugin({ // 配置index.html
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
filename: pathResolve('dist/index.html'),
template: pathResolve('src/index.html'),
chunks: ['manifest', 'vendor', 'index', ] // 配置index.html须要用的chunk块,即加载哪些JS文件,manifest模块管理的核心,必须第一个进行加载,否则会报错
}),
new htmlWebpackPlugin({ // 配置main.html
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
filename: pathResolve('dist/main.html'),
template: pathResolve('src/main.html'),
chunks: ['manifest', 'shake'] // 配置index.html须要用的chunk块,加载manifest.js,shake.js
}),
],
复制代码
执行npm run build
,成功构建了index.html
,main.html
。
至此,咱们摆脱了第三方脚手架的的禁锢,按部就班的搭建了属于本身的前端流程工具,作到了即改即用,功能俱全,快速便捷,复用性强的特色。但愿小伙伴能亲自动手,别总是纸上谈webpack
,要理解它的构建、优化原理,驾轻就熟得融入到本身的工程项目中,拒绝再用之前繁琐,不规范的开发流程,不作“CV工程师”,建立属于本身的知识体系、工做流程,提升前端的开发效率。
最后,本项目源码已部署在Github
上,并增长了许多额外优化(less
的支持,ESLint
检测,针对图片格式的压缩...),让你们能够直接下载体验,并协助项目开发,往后也会持续维护,但愿小伙伴们能够互相学习,提提建议。
GitHub地址:easy-frontend 一个快速,简单,易用的前端开发效率提高工具
Star该项目,就是大家对我最大的的鼓励!!
前端路上,不忘初心,祝你们早日发财!!