webpack4打包实战
一、模块化打包工具的由来
ES Module存在环境兼容问题,通过模块化方式划分的模块较多,网络请求频繁,在前端应用开发中不仅仅需要JavaScript代码需要模块化,随着应用的日益复杂,html,css同样也面临相同的问题,也就是说,所有的前端资源都需要模块化。
所需要的工具需要满足的条件:
- 新特性代码编译
- 模块化JavaScript打包
- 支持不同文件类型的资源模块
打包工具:解决前端整体的模块化,并不单指JavaScript模块化
二、模块打包工具概要
核心:
- 模块打包器(Module bundler)
- 模块加载器(loader)
- 代码拆分(code splitting)
- 资源模块(asset module)
打包工具解决的是前端整体的模块化,并不单指javascript模块化
三、webpack 快速上手
具体过程如下
- 创建package.json,执行命令:
yarn init --yes
- 安装webpack:
yarn add webpack webpack-cli --dev
- 查看webpack版本:
yarn webpack --version
或npx webpack -v
- 打包:执行
yarn webpack
,此时会生成一个dis文件夹,文件中有main.js - 修改html的js引入路径,并去掉
type=module
- 也可以在script中定义:
"scripts": {
"build":"webpack"
},
//后期只使用:yarn build即可
// heading.js
export default () => {
const element = document.createElement('h2')
element.textContent = 'Hello World!'
element.addEventListener('click',()=>{
alert('hello webpack')
})
return element
}
//index.js
import createHeading from './heading.js'
const heading = createHeading()
document.body.append(heading)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <script src="src/index.js" type="module"></script> -->
<script src="dist/main.js"></script>
</body>
</html>
四、webpack配置文件
webpack.config.js
是运行在nodeJs中的一个文件,需要根据commonJs的规范编写代码,文件导出一个对象,通过导出的对象属性完成相应的配置选项
const path = require('path')
module.exports = {
//入口,打包webapck的入口文件地址,相对路径时,./不能省略
entry: './src/main.js',
//output:指定输出路径,filename:文件名称
output:{
filename:'bundle.js' ,
//path是绝对路径
path: path.join(__dirname,"output")
}
}
五、webpack工作模式
- production:生产模式会默认启动优化,优化我们的打包结果
- development:开发模式,会自动优化打包的速度,添加一些调试过程中的辅助到代码中
- none:原始状态的打包,不会做任何处理
两种方式:
- 通过
yarn webpack --mode
模式名称来执行. - 在配置文件中指定:
mode: "none"
六、webpack资源模块加载
直接打包css文件
ERROR in ./src/main.css 1:4
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> body{
| margin: 0;
| padding:20px;
error Command failed with exit code 2.
需要安裝相应的loader
yarn add css-loader --dev
yarn add style-loader --dev
这里的编译顺序是先用css-loader将css代码编译,再交给style-loader插入到网页里面去
修改配置:
entry: './src/main.css',
module:{
rules:[
{
test: /.css$/,
//注意:执行顺序从右向左
use:[
//style-loader是将css-loader处理后的结果,通过style的形式追加到页面上
'style-loader',
'css-loader'
]
}
]
}
loader的执行顺序是从后往前执行
七、webpack导入资源模块
webpack的入口文件可以是js,css的类型文件,但由于前端项目是由JS驱动,所以webpack将入口文件设置为JS文件,需要用到CSS时,直接在JS文件中通过import导入,import './main.css'
webpack建议根据代码需要在JS中动态导入资源文件,因为需要资源的不是应用,而是代码。因为是JavaScript驱动了整个前端应用,这样做的好处是逻辑合理,JS确实需要这些资源文件,确保上线资源不缺失,都是必要的
八、webpack资源文件加载器
图片,字体等其他资源文件,这些是没有办法通过js的方式进行表示,对于这些文件,我们就需要使用文件资源加载器file loader,安装命令:yarn add file-loader --dev
//引入图片文件,返回值为路径,
import icon from './icon.jpg'
//创建image
const img = new Image()
//设置路径
img.src = icon
//追加
document.body.append(img)
webpack.config.js中的rules添加
{
test: /.jpg$/,
use:'file-loader'
}
文件加载器工作过程:
webpack在打包时遇到图片文件,然后根据配置文件中的配置匹配到对应的文件加载器,此时文件加载器开始工作,先将导入的文件拷贝输出到指定的目录,将拷贝完之后的路径作为返回值返回,然后资源就被发布,可在浏览器看到。
九、webpack URL 加载器
Data URLs与url-loader
yarn add url-loader --dev
安装url-loader 的时候也一定要安装file-loader
- 小文件使用Data URLs,减小请求次数
- 大文件单独存放,提高加载速度
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024 // 10 KB
}
}
}
]
}
- 超过10kb文件单独提取存放
- 小于10kb文件转换为Data URLs嵌入代码中
url-loader和file-loader区别
url-loader可以将图片转为base64字符串,能更快的加载图片,一旦图片过大,
就需要使用file-loader的加载本地图片,故url-loader可以设置图片超过多少字节时,使用file-loader加载图片。
十、webpack常用加载器分类
-
编译转换类,转换为JS代码,如
css-loader
-
文件操作类,将资源文件拷贝到输出目录,将文件访问路径向外导出,如:
file-loader
-
代码检查器,统一代码风格,提高代码质量,如:
eslint-loader
十一、webpack与ES 2015
webpack只是打包工具,不会处理ES6或者其他新特性,所以可以使用加载器来编译转化代码,babel-loader
,babel-loader
依赖于babel的核心模块,@babel/core
和@babel/preset-env
yarn add babel-loader @babel/core @babel/preset-env --dev
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
},
exclude: /(node_modules)/ //一定不能省略,否则会出错
}
}
- Webpack只是打包工具
- 加载器可以用来编译转换代码
十二、webpack加载资源的方式
1、遵循ES Modules标准的import声明
//引入图片文件,返回值为路径,
import icon from './icon.jpg'
//创建image
const img = new Image()
//设置路径
img.src = icon
//追加
document.body.append(img)
2、遵循CommonJS标准的require函数。对于ES的默认导出,要通过require(’./XXX’).default的形式获取
const createHeading = require('./heading.js').default
const icon = require('./icon.png')
3、循AMD标准的define函数和require函数
- 遵循ES Modules标准的import声明
- 遵循CommonJS标准的require函数
- 遵循AMD标准的define函数和require函数
但不要混合使用这些标准,以免降低代码的可维护性
4、Loader加载的非JavaScript也会触发资源加载
@import url(reset.css);
body{
margin: 0;
background-color: #582;
color: red;
background-image: url('./icon.jpg');
background-size: cover;
}
css-loader在处理css代码时,遇到了background属性中的url函数,发现是引入的资源文件是png格式的文件,则将这个资源文件 交给url-loader处理
对于html文件,也会引入资源文件,例如img的src
html-loader
yarn add html-loader --dev
// footer.html
<footer>
<!-- <img src="./icon.jpg" width="250"> -->
<a href='./icon.jpg'>look</a>
</footer>
// main.js
import footerHtml from './footer.html'
document.write(footerHtml)
//配置
{
test: /.html$/,
use:{
loader:'html-loader',
options:{
attributes:{
list: [
{
tag: 'img',
attribute: 'src',
type: 'src'
},
{
tag: 'a',
attribute: 'href',
type: 'src'
}
]
}
}
}
}
webpack模块加载方式
- 遵循ES Modules标准的import声明
- 遵循CommonJS标准的require函数
- 遵循AMD标准的define函数和require函数
- *样式代码中的@import指令和url函数
- *HTML代码中的图片标签的src属性
十三、webpack核心工作原理
在项目当中一般会散落各种各样的代码以及资源文件,webpack会根据配置找到入口文件,一般情况下这个文件是一个JavaScript文件,然后它会顺着入口文件的代码,根据代码中的import或者require之类语句,解析这个文件所依赖的资源文件模块,然后分别去解析每个资源模块对应的依赖,最后形成一颗依赖树,然后webpack递归遍历这个依赖树,然后找到每个节点对应的资源文件,最后根据我们配置文件的rules属性找到对应的加载器,然后交给加载器去加载转义这个模块,最后得到的加载结果放到打包结果当中,也就是bundle.js当中从而实现我们整个项目的打包
简单来说:
入口文件 ==> 检查代码是否有import/require语句 ==> 解析资源文件模块 解析依赖 ==> 形成依赖树 ==>
递归遍历依赖树 ==> 找到每个节点对应的资源文件 ==> 根据配置文件的rules属性 ==> 加载转义 ==> 放到打包文件
loader机制是webpack的核心
十四、webpack开发一个loader
开发一个markdowm-loader
// markdown-loader.js
const marked = require('marked') // yarn add marked --dev
module.exports = source => {
// console.log(source)
// return 'console.log("hello")'
const html = marked(source)
// return `module.export = ${JSON.stringify(html)}`
// return `export default ${JSON.stringify(html)} `
//返回html,字符串交给下一个loader处理
return html
}
about.md
# hello
qiuqiu
module: {
rules: [
{
test: /.md$/,
use: ['html-loader', './markdown-loader.js']
}
]
}
其实loader就相当于是一个管道,就是一个从输入到输出之间的一个转换。
十五、Webpack插件机制
(1)插件机制是Webpack当中一个核心特性,目的是增强webpack在项目自动化方面的能力。
(2)loader就是负责实现我们项目当中各种各样资源模块加载。从而去实现整体项目的打包。
(3)plugin则是用来去解决项目中除了资源加载以外其他的一些自动化工作。例如,可以帮我们去实现,自动在打包之前去清除DIV的目录。
十六、webpack自动清除目录插件
自动在打包之前去清除dist目录
yarn add clean-webpack-plugin --dev
webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
plugins: [
new CleanWebpackPlugin()
]
十七、自动生成HTML插件
自动生成HTML插件
yarn add html-webpack-plugin --dev
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin()
]
根据模板生成页面
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
}
})
生成多个html
plugins: [
new CleanWebpackPlugin(),
// 用于生成index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 用于生成about.html
new HtmlWebpackPlugin({
filename: 'about.html'
})
]
十八、拷贝文件
拷贝那些不需要参与打包的资源文件到输出目录
安装:yarn add copy-webpack-plugin --dev
const CopyWebpackPlugin = require('copy-webpack-plugin')
new CopyWebpackPlugin({
patterns: ['public']
})
十九、webpack开发一个插件
相比于Loader,Plugin拥有更宽的能力范围,Plugin通过钩子机制实现。
Webpack要求插件必须是一个函数或者是一个包含apply方法的对象。
自定义插件,MyPlugin
class Myplugin {
apply(compiler) {
console.log("qidong")
compiler.hooks.emit.tap('Myplugin', compilation => {
for (const name in compilation.assets) {
// console.log(name) 输出的是文件名称
console.log(compilation.assets[name].source()) //文件内容
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\//g, '')
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
使用:
plugins:[
new Myplugin()
]
二十、webpack开发体验问题
按照上图的方式进行,显得很原始,为了提高开发效率,我们做出设想
- 以HTTP Server运行,而不是进行文件预览
- 自动编译构建+自动刷新,可以减少开发中重复的操作
- 支持sourceMap
二十一、自动编译
watch工作模式:监听文件变化,自动重新打包,我们只需专心编码,不用重复打包
yarn webpack --watch //启动命令时添加参数 --watch
二十二、自动刷新浏览器
安装BrowserSync: yarn add browser-sync
启动: browser-sync dist --files '**/*'
二十三、webpack Dev server
提供开发服务器,集成“自动编译”,“自动刷新浏览器”
安装:yarn add webpack-dev-server --dev
运行:yarn webpack-dev-server --open
静态资源访问:
通過webpack打包输出的文件都可以被访问到,其他静态资源也需要serve,需要额外的告诉webserver
devServer:{
contentBase: './public'
}
contentBase额外为开发服务器指定查找资源目录
二十四、webpack Dev server代理API
使用CORS解决跨域问题的前提是API要支持CORS
webpack Dev server支持代理配置
devServer:{
contentBase: './public',
proxy:{
'/api': {
// http://localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com',
// 路径重写,http://localhost:8080/api/users -> https://api.github.com/users
pathRewrite: {
'^/api': ''
},
// 不能使用localhost:8080作为请求GitHub的主机名
changeOrigin: true, // 以实际代理的主机名去请求
}
}
},
二十五、Source Map介绍
通过构建编译的操作我们可以将开发阶段的源代码转化为可以在开发环境运行的代码,这是一种进步,但是,这就会导致:运行代码和源代码之间有很大的差异,这种情况下,如果我们需要调试应用,将无从下手。SourceMap很好的解决了这个问题。
SourceMap解决了源代码与运行代码不一致所产生的问题。
二十六、Webpack配置SourceMap
在webpack.config.js中配置:devtool:'source-map'
,然后运行yarn webpack
,查看生成的文件,会看到生成了bundle.js.map
,并且在bundle.js
末尾有://# sourceMappingURL=bundle.js.map
Webpack 支持sourceMap 12种不同的方式,每种方式的速度和效果各不相同。效果最好的速度最慢,速度最快的一般没有什么效果
二十七、WebPack devtool 模式对比与选择
- eval- 是否使用eval执行代码模块
- cheap- Source map是否包含行信息
- module-是否能够得到Loader处理之前的源代码
- inline- SourceMap 不是物理文件,而是以URL形式嵌入到代码中
- hidden- 看不到SourceMap文件,但确实是生成了该文件
- nosources- 没有源代码,但是有行列信息。保护源代码不暴露
开发模式推荐使用:eval-cheap-module-source-map,原因有:
- 代码每行不会太长,可以没有列
- 代码经过Loader转换后的差异较大
- 首次打包速度慢无所谓,重新打包相对较快
生产模式推荐使用:none,原因有:
- Source Map会暴露源代码
- 调试是开发阶段的事情
- 对代码实在没有信心可以使用
nosources-source-map
二十八、自动刷新,webpack HMR
当检测到页面变化则重新编译上传,会导致页面刷新的问题
HMR(Hot Module Replacement) 模块热替换,应用运行过程中,实时替换某个模块,应用运行状态不受影响。
HMR是webpack中最强大的功能之一,极大程度的提高了开发者的工作效率。
HMR已经集成在了webpack-dev-server中,运行webpack-dev-server --hot
,也可以通过配置文件开启.
配置:
const webpack = require('webpack')
devServer:{
hot:true
}
plugins:[
new webpack.HotModuleReplacementPlugin()
]
HMR并不是对所有文件开箱即用,
Q:为什么样式文件的热更新开箱即用?答:因为样式文件通过loader处理,在style-loader中已经做了热更新处理,所以不用额外操作
css文件时有规律的,所以直接覆盖掉之前的css文件就可以实现,不需要刷新页面
js文件是没有规律的,如果想实现不刷新页面,需要手动处理
webpack没办法提供一个通用的方案,需要根据自己业务处理
处理图片模块热替换
Webpack HMR注意事项
1.处理HMR的代码报错会导致自动刷新
使用hot的话看不到错误,因为页面刷新了,错误信息被清除
hot 和 hotOnly 的区别是在某些模块不支持热更新的情况下,前者会自动刷新页面,后者不会刷新页面,而是在控制台输出热更新失败
二十九、webpack生产环境优化
webpack建议我们为不同的环境创建不同的配置,
比如souce-map、HMR对于生产环境是冗余的
(1)配置文件根据环境不同导出不同配置
const path = require('path')
const { rule } = require('postcss')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
class Myplugin {
apply(compiler) {
console.log("qidong")
compiler.hooks.emit.tap('Myplugin', compilation => {
for (const name in compilation.assets) {
// console.log(name) 输出的是文件名称
//console.log(compilation.assets[name].source())
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\//g, '')
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
module.exports = (env, argv) => {
const config = {
//指定打包模式
mode: 'none',
//入口,打包webapck的入口文件地址,相对路径时,./不能省略
entry: './src/main.js',
//output:指定输出路径,filename:文件名称
output: {
filename: 'bundle.js',
//path是绝对路径
path: path.join(__dirname, "dist"),
publicPath: 'dist/'
},
devtool: 'source-map',
devServer: {
contentBase: './public',
hot: true,
proxy: {
'/api': {
// http://localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com',
// 路径重写,http://localhost:8080/api/users -> https://api.github.com/users
pathRewrite: {
'^/api': ''
},
// 不能使用localhost:8080作为请求GitHub的主机名
changeOrigin: true, // 以实际代理的主机名去请求
}
}
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.jpg$/,
use: 'file-loader'
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024
}
}
},
{
test: /.md$/,
// use: './markdown-loader'
use: [
'html-loader',
'./markdown-loader'
]
},
{
test: /.js$/,
// use:'babel-loader'
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /(node_modules)/ //一定不能省略,否则会出错
},
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attributes: {
list: [
{
tag: 'img',
attribute: 'src',
type: 'src'
},
{
tag: 'a',
attribute: 'href',
type: 'src'
}
]
}
}
}
},
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 用于生成about.html
new HtmlWebpackPlugin({
filename: 'about.html'
}),
//开发期间最好不要使用
// new CopyWebpackPlugin({
// patterns: ['public']
// }),
//new Myplugin()
new webpack.HotModuleReplacementPlugin()
]
}
//判断
if (env === 'production') {
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: ['public']
})
]
}
return config
}
//yarn webpack --env production
传递一个参数去测试
2、不同环境对应不同配置文件
const {merge} = require(‘webpack-merge’)
创建三个文件,webpack.dev.js、webpack.prod.js、webpack.common.js
webpack.dev.js:
const {merge} = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode:'development',
}
)
webpack.prod.js:
const common = require('./webpack.common')
const {merge} = require('webpack-merge')
module.export = merge(common, {
mode: 'production',
})
webpack.common.js:
const path = require('path')
const { rule } = require('postcss')
const webpack = require('webpack')
module.exports = {
entry: './src/main.js',
//output:指定输出路径,filename:文件名称
output: {
filename: 'bundle.js',
//path是绝对路径
path: path.join(__dirname, "dist"),
publicPath: 'dist/'
},
devtool: 'source-map',
devServer: {
contentBase: './public',
hot: true,
proxy: {
'/api': {
// http://localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com',
// 路径重写,http://localhost:8080/api/users -> https://api.github.com/users
pathRewrite: {
'^/api': ''
},
// 不能使用localhost:8080作为请求GitHub的主机名
changeOrigin: true, // 以实际代理的主机名去请求
}
}
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.jpg$/,
use: 'file-loader'
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024
}
}
},
{
test: /.md$/,
// use: './markdown-loader'
use: [
'html-loader',
'./markdown-loader'
]
},
{
test: /.js$/,
// use:'babel-loader'
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /(node_modules)/ //一定不能省略,否则会出错
},
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attributes: {
list: [
{
tag: 'img',
attribute: 'src',
type: 'src'
},
{
tag: 'a',
attribute: 'href',
type: 'src'
}
]
}
}
}
},
]
}
}
三十、webpack DefinePlugin
为代码注入全局成员,在production下,插件默认启动起来,并且注入一个全局变量process.env.NODE_ENV
const webpack = require('webpack')
module.exports = {
mode:'none',
entry:'./src/main.js',
output:{
filename:'bundle.js',
},
plugins:[
new webpack.DefinePlugin({
// API_BASE_URL:"123"
API_BASE_URL:'"123"'
})
]
}
三十一、webpack体验Tree-Shaking
Tree-Shaking在生产模式下自动开启,可以摇掉代码中未引用到的代码(dead-code),Tree-Shaking并不是webpack中的某一个配置选项,是一组功能搭配使用后的效果。
使用
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true //webpack合并模块
}
usedExports负责标记【枯树叶】
minimize负责【摇掉】它们
concatenateModules:尽可能将所有模块合并输出到一个函数中,既提升了运行效率,又减少了代码体积
注意
纠正使用Babel-Loader,会导致Tree-Shaking失效的问题:因为Tree-Shaking前提是ES Modules,由Webpack打包的代码必须使用ESM,为了转化ES中的新特性,会使用babel处理新特性,就有可能将ESM转化CommonJS,而我们使用的@babel/preset-env这个插件集合就会转化ESM为CommonJS,所以Tree-Shaking会不生效。但是在最新版babel-loader关闭了转换ESM的插件,所以使用babel-loader不会导致Tree-Shaking失效
三十二、sideEffects副作用
**副作用:**模块执行时除了导出成员之外所做的事情
sideEffects一般用于npm包标记是否有副作用
webpack4.0中新增的新特性 - sideEffects 副作用,允许我们通过配置的方式标识代码是否有副作用,副作用指
的是模块执行时除了导出成员之外所做的事情。如果设置没有副作用,则没有用到的模块则不会被打包。
配置方法:
(1)在webpack.config.js中配置开启副作用功能
optimization: {
//开启功能
sideEffects:true,
usedExports: true,
//是否压缩bundle.js文件
//minimize: true
}
(2)在package.json中标识代码设置没有副作用
使用sideEffects的前提是确保你的代码真的没有副作用,否则在webpack打包时就会误删掉有副作用的代码。比如在原型上添加方法
打包之后是不会被打包进来的
则这些方法就是副作用,则此时应该忽略掉,配置如下:
三十三、webpack代码分割
目前导报是所有的代码都会被打包到一起,如果打包文件过多,bundle会非常大。而并不是每个模块在启动时都是必要的,所以需要分包、按需加载。资源太大了不行,太碎了太零散了也不行。太大了会影响加载速度;太碎了会导致请求次数过多,因为在目前主流的HTTP1.1有很多缺陷,如同域并行请求限制、每次请求都会有一定的延迟,请求的Header浪费带宽流量。所以模块打包时有必要的。
webpack两种实现分包方式:
- 多入口打包
- 动态导入
(1)多入口打包
适用于传统的多页面应用,一个页面对应一个打包入口,不同页面公共部分单独提取。
配置文件:webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
mode:'none',
entry:{
index: './src/index.js',
album: './src/album.js'
},
output:{
filename:'[name].bundle.js'
},
module:{
rules:[
{
test:/\.css$/,
use:[
//style-loader是将css-loader处理后的结果,通过style的形式追加到页面上
'style-loader',
'css-loader'
]
}
]
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename:'index.html',
chunks:['index'] //引入的js文件
}),
new HtmlWebpackPlugin({
filename:'album.html',
chunks:['album'] //引入的js文件
}),
]
}
提取公共部分
如果某一个文件被多个文件引用,则可以使用配置的方式对公共部分进行提取。配置如下:
optimization: {
splitChunks: {
chunks: 'all'
}
}
(2)动态导入
需要用到某个模块时,再加载这个模块,动态导入的模块会被自动分包。通过动态导入生成的文件只是一个序号,可以使用魔法注释指定分包产生bundle的名称。相同的chunk名会被打包到一起。
import('./post/posts').then({default: posts}) => {
mainElement.appendChild(posts())
}
魔法注释,分包产生的文件名是数字,如果想自定义名称,则可以通过魔法注释的方式进行,如果注释的文件名一致,则会打包到同一个文件中。
import(/* webpackChunkName: 'component' */'./post/posts').then({default: posts}) => {
mainElement.appendChild(posts())
}
三十四、MiniCssExtractPlugin
提取css到单个文件,通过这个插件可以实现css的按需加载。
安装:yarn add mini-css-extract-plugin --dev
使用:
const MiniCssExtracPlugin = require('mini-css-extract-plugin')
plugins: [
new MiniCssExtracPlugin()
]
module: {
rules: [
{
test: /\.css$/,
use: [
//style-loader是将css-loader处理后的结果,通过style的形式追加到页面上
//'style-loader',
MiniCssExtracPlugin.loader,
'css-loader'
]
}
]
}
三十五、OptimizeCssAssetsWebpackPlugin压缩输出的css文件
webpack支持对js文件的压缩,但是对其他为文件没有作用,所以我们需要使用插件对其进行压缩。
安装: yarn add optimize-css-assets-webpack-plugin --dev
安装:yarn add terser-webpack-plugin --dev
//压缩js
const OptimizeCssAssetWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
optimization: {
minimizer: [ //配置之后 就会覆盖掉webpack内部的js压缩插件,要重新配置
new TerserWebpackPlugin(), //webpack认为要自定义压缩
new OptimizeCssAssetWebpackPlugin(),
]
},
plugins: [
new OptimizeCssAssetWebpackPlugin()
]
三十六、webpack输出文件名hash
三种方式:
1、hash
项目级别的
new MiniCssExtracPlugin({
filename:'[name]-[hash].bundle.css'
}),
2、chunkhash
output: {
filename: '[name]-[chunkhash].bundle.js'
},
同一路是相同的
3、contenhash
文件级别
output: {
filename: '[name]-[contenhash].bundle.js'
},
指定长度: hash:number,number=8推荐使用
output: {
filename: '[name]-[chunkhash:8].bundle.js'
},
loader总结
css-loader、style-loader、file-loader、url-loader、html-loader、babel-loader
plugin总结
clean-webpack-plugin、html-webpack-plugin、DefinePlugin、copy-webpack-plugin、mini-css-extract-plugin、optimize-css-assets-webpack-plugin
三十七、Rollup打包
rollup是Esmodule的打包器,与webpack作用很类似,相对于webpack,rullup更小巧,仅仅是一款ESM打包器,Rollup并不支持类似HMR这种高级特性。Rollup并不是要与webpack竞争,初衷只是希望提供一个充分利用ESM各项特性的高效打包器。
1、快速上手
三个文件,logger.js,message.js,index.js
logger.js
export const log = msg => {
console.log('=======INFO==========')
console.log(msg)
console.log('=====================')
}
export const error = msg => {
console.log('=======ERROR==========')
console.log(msg)
console.log('=====================')
}
message.js
export default {
hi: 'hey,i am qiuqiu'
}
index.js
//导入模块成员
import { log } from './logger'
import messages from './messages'
const hi = messages.hi
console.log(hi)
执行打包命令
// 安装
yarn add rollup --dev
//打包,将文件输出到dist目录下的bundle.js文件中
yarn rollup ./src/index.js --format iife --file dist/bundle.js
打包以后的bundle.js
(function () {
'use strict';
var messages = {
hi: 'hey,i am qiuqiu'
};
//导入模块成员
const hi = messages.hi;
console.log(hi);
}());
打包结果非常简洁,没有多余代码
rollup会自动开启tree-shaking, tree-shaking也是最早在rollup中提出的
2、Rollup配置文件
我们可以使用配置文件的方式对rollup进行配置,配置如下:
// rollup.config.js
export default {
//入口文件
input:'./src/index.js',
output:{
file:'dist/bundle.js',
format:'iife'
}
}
也可以创建多个配置文件,在执行打包命令的时候指定相应的打包文件
打包命令:yarn rollup --config
,指定打包文件:yarn rollup --config rollup.config.js
3、Rollup使用插件
Rollup自身的功能就是对ESM进行合并打包,如果项目有需要更高级的需求,如加载其他类型资源模块,导入CommonJS模块,编译ES新特性,对于这些高级的需求,Rollup支持使用插件的方式扩展实现,插件是Rollup唯一的扩展方式。
安装json插件: yarn add rollup-plugin-json --dev
使用插件:
rollup.config.json
import json from 'rollup-plugin-json'
export default {
input:'./src/index.js',
output:{
file:'dist/bundle.js',
format:'iife'
},
plugins:[
//执行的结果
json()
]
}
index.js
//导入模块成员
import { log } from './logger'
import messages from './messages'
import {name,version} from '../package.json'
const hi = messages.hi
console.log(hi)
console.log(name,version)
bundle.js
(function () {
'use strict';
var messages = {
hi: 'hey,i am qiuqiu'
};
var name = "rollup-demo1";
var version = "1.0.0";
//导入模块成员
const hi = messages.hi;
console.log(hi);
console.log(name,version);
}());
没有引用的没有被打包进去
4、Rollup加载npm
安装:yarn add rollup-plugin-node-resolve --dev
,yarn add lodash-es --dev
rollup-resolve-node-plugin,通过这个插件我们可以根据模块名称直接导入相应的模块
配置文件rollup.json.js
import json from 'rollup-plugin-json'
import resolve from 'rollup-plugin-node-resolve'
export default {
input:'./src/index.js',
output:{
file:'dist/bundle.js',
format:'iife'
},
plugins:[
//执行的结果
json(),
resolve()
]
}
打包入口文件index.js
//导入模块成员
import _ from 'lodash-es'
import { log } from './logger'
import messages from './messages'
import {name,version} from '../package.json'
const hi = messages.hi
console.log(hi)
console.log(name,version)
console.log(_.camelCase('hello world'))
打包結果:
var name = "rollup-demo1";
var version = "1.0.0";
//导入模块成员
const hi = messages.hi;
console.log(hi);
console.log(name,version);
console.log(lodash.camelCase('hello world'));
5、加载CommonJS模块
rollup只处理ESM的模块打包,如果我们在代码中导入commonJs模块,默认是hi不被支持的,但是目前还是有很多npm模块使用commonJs导出模块成员,为了兼容这些模块,官方为我们提供了rollup-plugin-commonJs插件。
安装:yarn add rollup-plugin-commonjs
配置文件rollup.config.js
import json from 'rollup-plugin-json'
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
export default {
input:'./src/index.js',
output:{
file:'dist/bundle.js',
format:'iife'
},
plugins:[
//执行的结果
json(),
resolve(),
commonjs()
]
}
index.js
//导入模块成员
import cjs from './cjs-module'
console.log(cjs)
cjs-module.js
module.exports = {
foo:'foo'
}
bundle.js
...
var cjsModule = {
foo:'foo'
};
//导入模块成员
console.log(cjsModule);
...
6、代码拆分
在最新版本中,rollup已经支持代码拆分,我们可以使用动态导入的方法实现按需加载,rollup内部也处理代码的拆分,也就是分包。
index.js
import('./logger').then(({log})=>{
log('code splitting~~~');
})
rollup.config.js
output:{
dir:'dist',
format:'amd'
},
logger打包以后的文件内容
define(['exports'], function (exports) { 'use strict';
const log = msg => {
console.log('=======INFO==========');
console.log(msg);
console.log('=====================');
};
const error = msg => {
console.log('=======ERROR==========');
console.log(msg);
console.log('=====================');
};
exports.error = error;
exports.log = log;
});
7、多入口打包
rullup支持多文件打包
album.js
import { log } from './logger'
console.log(123)
const hi = log("456")
console.log(hi,123)
index.js
import { log } from './logger'
console.log(123)
const hi = log("123")
console.log(hi,456)
webpack.config.js
export default {
input:['./src/index.js','./src/album.js'],
output:{
dir:'dist',
format:'amd'
}
8、Rollup/webpack选用
Rollup
优点
- 输出结果更加扁平化,执行效率更高
- 自动移除为使用的代码
- 打包结果依旧可读
缺点
- 加载非ESM的第三方模块比较复杂,需要配置一大堆插件
- 模块最终被打包到一个函数中,无法实现HMR热开发
- 浏览器环境中,代码拆分功能依赖AMD库
如果我们正在开发应用程序,需要大量引入第三方插件,需要分包,则选择webpack
如果我们正在开发一个框架或者类库,则使用rollup打包 ,但也不是绝对的标准
三十八、Parcel
Parcel是零配置的前端应用打包器
yarn init
yarn add parcel-bundler --dev
//打包
yarn parcel src/index.html
自动安装依赖
编写代码的过程中,parcel会自动安装依赖
可以直接加载其他类型的资源文件,比如css、图片,不需要安装相应loader
三十九、规范化标准ESLint
1、规范化介绍
为什么要有规范化标准
- 软件开发需要多人协同
- 不同开发者具有不同的编码习惯和喜好
- 不同的喜好增加项目维护成本
- 每个项目或者团队需要明确统一的标准
哪里需要规范化标准
- 代码、文档、甚至是提交日志
- 开发过程中人为编写的成果物
- 代码标准化规范最为重要
实施规范化的方法
- 编码前人为的标准约定
- 通过工具实现 Lint
常见的规范化实现方式
- ESLint 工具使用
- 定制 ESLint 校验规则
- ESLint 对 TypeScript 的支持
- ESLint 结合自动化工具或者 Webpack
- 基于 ESLint 的衍生工具
- Stylelint 工具的使用
2、ESLint 介绍
- 最为主流的 JavaScript Lint 工具 监测 JS 代码质量
- ESLint 很容易统一开发者的编码风格
- ESLint 可以帮助开发者提升编码能力
3、ESLint 安装步骤
- 初始化项目
npm init
- 安装 ESLint 模块为开发依赖
npm i eslint --save-dev
- 通过 CLI 命令验证安装结果
4、ESLint 快速上手
ESLint 检查步骤
- 编写“问题”代码
- 使用 eslint 执行检测
- 完成 eslint 使用配置
npx eslint --init
第一个选项是检查语法,第二个选项检查语法并找错误代码,第三个选项前两项加代码风格校验
npx eslint .\01-prepare.js --fix
可以自动解决代码风格问题,语法问题解决不了