模块化开发是一种思想,随着前端项目的日益庞大。为了使我们开发协作更加高效,互不影响。将编写的代码模块化,更利于协作与维护。使得开发更加高效。
CommonJs规范
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过 module.exoprts 导出成员
- 通过 require 函数载入模块
CommonJS是以同步模式加载模块
因为node执行机制是在启动时加载模块,执行过程中不会加载模块,这种方式在node中不会有问题。但是在浏览器端,使用每次页面加载都会导致大量同步请求出现。所以当时有一个特地为浏览器设定的规范 AMD
AMD(Asynchronous Module Definition)异步模块定义规范
用define定义一个模块
//定义一个模块
define('module',['jquery','./module2'],function($,module2){
return {
fn:function(){ console.log('hello')}
}
})
//加载一个模块
require(['./module'],function(module){
module.fn()
})
- AMD使用起来相对复杂
- 模块JS文件请求频繁 页面效率低下
模块化标准规范
在nodeJS中 使用 CommonJS 规范
在浏览器环境中 使用 ESModules 规范
ES Modules
基本特性
- ESM 自动采用严格模式 忽略 'use strict'
- 每个ESM 都是运行在单独的私有作用域中
- ESM是通过 CORS 的方式请求外部JS模块的 必须在http serve环境中运行
- ESM script标签会延迟执行脚本 相当于添加defer属性
<!-- 通过 script 添加 type=module 属性,就可以使 ES Module标准执行js -->
<script type="module">
console.log('sssssss')
</script>
导入导出
export const foo = 'esm'
import { foo } from './module.js'
ES Module 浏览器环境 Polyfill
在支持新语法的浏览器中 脚本执行一次,引入polyfill也会执行一次代码,就会出现执行两次现象。此时,引入nomodule 属性,就只会在不支持的浏览器中工作。(只适用于开发阶段,生产环境还是要先进行编译处理)
<!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>ES Module 浏览器环境 Polyfill</title>
</head>
<body>
<script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
<script type="module">
import { foo } from './module.js'
console.log(foo)
</script>
</body>
</html>
node.js中使用ES Module
js文件扩展名修改为.mjs后缀
命令行启动 node --experimental-modules index.mjs (启用ESModule实验特性)
import { foo, bar } from './module.mjs'
console.log(foo, bar)
// 此时我们也可以通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')
// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')
// 对于第三方的 NPM 模块也可以通过 esm 加载
import _ from 'lodash'
_.camelCase('ES Module')
// 不支持,因为第三方模块都是导出默认成员
// import { camelCase } from 'lodash'
// console.log(camelCase('ES Module'))
- ES Modules 中可以导入CommonJS 模块
- CommonJS 中不能导入 ES Modules 模块
- CommonJS 始终只会导出一个默认成员
- import 不是解构导出对象
使用babel插件兼容ES Modules
yarn add @babel/node @babel/core @babel/preset-env --dev
yarn babel-node index.js --presets=@babel/preset-env
或者添加 .babelrc 文件
{
"presets":["@babel/preset-env"]
}
perset只是一个插件集合 具体使用插件来进行转换
yarn remove @babel/preset-env
先移除preset-env
yarn add @babel/plugin-transform-modules-commonjs --dev
.babelrc 文件
{
"plugins":["@babel/plugin-transform-modules-commonjs"]
}
webpack打包
安装 cnpm i webpack webpack-cli --dev
使用webpack
npx webpack
webpack会按照约定 将 src/index.js --> dist/main.js
如果需要定制化配置,需要在项目根目录下创建 webpack.config.js文件 它是运行在node环境下的一个js文件,所以我们呢需要按照commonJS规范去配置它
const path = require('path')
module.exports = {
entry: './src/index.js',//打包入口
output: {
filename: 'bundle.js',//输出文件名
path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
}
}
图片.png
webpack工作模式
webpack4增加了工作模式,这种用法大大减小了工作的复杂程度。针对不同环境的几种预设配置。
指定打包模式,默认是production模式 会对代码自动进行压缩等优化。
指定生产模式
npx webpack --mode production
指定开发模式
npx webpack --mode development
指定none模式
npx webpack --mode none
运行最原始的打包 不会进行额外处理
或者在配置文件中配置
module.exports = {
mode:'development',//工作模式
entry: './src/index.js',//打包入口
output: {
filename: 'bundle.js',//输出文件名
path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
}
}
webpack资源模块加载
内部的loader只能处理js文件,css等其它类型文件需要借助其它loaders进行处理。
打包css文件 需要借助css-loader
cnpm i css-loader --dev
css-loader作用是将css文件转换成js的一个模块,只是转换并没有使用,此时需要安装style-loader使用
cnpm i style-loader --dev
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.css',//打包入口
output: {
filename: 'bundle.js',//输出文件名
path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
},
module: {
//针对其它资源加载规则的一个配置
//每个规则对象需要设置两个属性 test属性 是一个正则表达式用来匹配打包过程遇到的文件路径 use属性 用来指定匹配的文件使用的loader
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader']//多个loader从后往前执行
}
]
}
}
Loader是webpack的核心特性
借助于Loader就可以加载任何类型的资源
webpack文件资源加载器
引入一些资源,如图片,字体等
借助于file-loader
cnpm i file-loader --dev
file-loader相当于对资源文件进行拷贝至目标目录,然后将文件路径输出,我们就可以通过路径访问到资源。
//webpack.config.js
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/',//根目录设置
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: 'file-loader'
}
]
}
}
**webpack URL加载器
Data URLs 与 url-loader
Data URLs是一种特殊的url协议,它可以用来直接表示一个文件,当前url可以直接表示文件内容的形式。
图片.png
就不用发送http请求去请求文件资源。
可以使用url形式表示任何资源文件
借助于 url-loader
cnpm i url-loader --dev
- 小文件使用 Data URLs ,减少请求次数
- 大文件采取 file-loader 方式,单独提取存放,提高加载速度
rules:[
{
test:/.css$/,
use:['style-loader','css-loader']
},
{
test:/.png$/,
use:{
loader:'url-loader',
options:{
limit:10 * 1024 //10kb大小使用url-loader处理,超出的使用file-loader
}
}
}
]
该方式依赖于file-loader
webpack常用加载器分类
- 编译转换类
将加载到的资源模块,转换为js代码 如:css-loader - 文件操作类型
将加载到的资源拷贝至输出目录,同时导出文件访问路径 如:file-loader - 代码检查类
代码校验,统一代码风格,提升代码质量
webpack处理ES2015
因为完成模块打包工作,所以webpack会处理import与export,它并不可以转换其它的es6特性。
需要借助于编译loader, babel-loader
cnpm i babel-loader @babel/core @babel/preset-env --dev
module:{
rules:[
{
test:/.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
},
{
test:/.css$/,
use:['style-loader','css-loader']
},
{
test:/.png$/,
use:{
loader:'url-loader',
options:{
limit:10 * 1024 //10kb大小使用url-loader处理,超出的使用file-loader
}
}
}
]
}
- webpack只是一个打包工具
- 加载器可以用来编译转换代码
webpack 模块加载方式
- 遵循ES Modules 标准的 import 声明
-
遵循 CommonJS 标准的 require 函数
(对于esmodules默认导出,需要使用default)
图片.png - 遵循 AMD 标准的 define 函数和 require 函数
Loader加载的非 JavaScript 也会触发资源加载
如:样式代码中的 @important 指令和 ur 函数
html中默认img src属性会触发资源加载,需要给其它属性触发资源加载对html-loader进行配置。
{
test:/.html$/,
use:{
loader:'html-loader',
options:{
attrs:['img:src','a:href']
}
}
}
webpack 插件机制
增强webpack自动化能力
Loader实现资源模块加载,从而实现整体项目的打包。plugin 解决除了资源加载其它自动化工作。
比如:自动清除dist目录,拷贝不需要打包的资源文件。
webpack 常用插件
- 自动清除输出目录插件
clean-webpack-plugin
cnpm i clean-webpack-plugin --dev
//webpack.config.js
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'none',
entry: './src/main.js',//打包入口
output: {
filename: 'bundle.js',//输出文件名
path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
},
plugins: [
new CleanWebpackPlugin()
]
}
- 自动生成使用bundle.js的HTML
通过Webpack输出HTML文件
html-webpack-plugin
cnpm i html-webpack-plugin --dev
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin') //默认导出的就是,不需要解构
plugins: [
new HtmlWebpackPlugin ({
title:'webpack plugin',//设置html标题
meta:{//设置页面标签
viewport:'width=device-width'
},
template:'./src/index.html'//指定所使用的模板文件
})
]
npx webpack后 dist目录下就会生成一个index.html文件
生成多个页面
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
entry: './src/main.js',//打包入口
output: {
filename: 'bundle.js',//输出文件名
path: path.join(__dirname, 'dist'),//必须是一个绝对路径 需要载入node中的path模块获取路径
},
module: {
rules: [
{
test: /.md$/,
use: './markdown-loader'
}
]
},
plugins: [
new CleanWebpackPlugin(),
//用于生成 index.html
new HtmlWebpackPlugin({
title: 'webpack plugin',//设置html标题
meta: {//设置页面标签
viewport: 'width=device-width'
},
template: './src/index.html'//指定所使用的模板文件
}),
//生成多个html 生成about.html
new HtmlWebpackPlugin({
filename: 'about.html'
})
]
}
copy-webpack-plugin
在项目中 一些静态资源不需要处理,只需要将它们复制到打包后的目录中,使用该插件
cnpm i copy-webpack-plugin --dev
在webpack.config.js中导入
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins:[
//传入数组指定拷贝目录
new CopyWebpackPlugin ([
'public'
])
]
webpack插件 Plugin 通过钩子机制实现
webpack插件要求是一个函数或者是一个包含apply方法的对象
移除打包后注释插件
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
class MyPlugin {
//会在webpack启动时自动去调用
//清除打包后bundle.js中的注释
apply(compiler) {
//emit钩子在生成文件将要写入dist文件时
compiler.hooks.emit.tap('MyPlugin', compilation => {
//compilation 可以理解为此次打包的上下文 大伯啊结果会放在这个对象中
//compilation.assets所有打包文件 key:打包文件名称 compilation.assets[name].source()方法拿到文件内容
for (const name in compilation.assets) {
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
//覆盖原来值
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length//webpack内部要求必须的方法
}
}
}
})//传入两个参数 1.插件名称 2.需要挂载的函数
}
}
module.exports = {
mode: 'none',
entry: './src/main.js',//打包入口
output: {
filename: 'bundle.js',//输出文件名
path: path.join(__dirname, 'dist'),//必须是一个绝对路径 需要载入node中的path模块获取路径
},
module: {
rules: [
{
test: /.md$/,
use: './markdown-loader'
}
]
},
plugins: [
new CleanWebpackPlugin(),
//用于生成 index.html
new HtmlWebpackPlugin({
title: 'webpack plugin',//设置html标题
meta: {//设置页面标签
viewport: 'width=device-width'
},
template: './src/index.html'//指定所使用的模板文件
}),
//生成多个html 生成about.html
new HtmlWebpackPlugin({
filename: 'about.html'
}),
//自定义插件 移除注释插件
new MyPlugin()
]
}
通过在生命周期的钩子中挂在函数实现扩展
webpack增强开发体验
实现自动编译
使用watch模式,监听文件变化,自动重新打包
在运行webpack时加上wacth
npx webpack --watch
此时webpack会以监视模式运行
实现自动刷新页面
BrowserSync 实现自动刷新功能
browser-sync dist --files "*/"
监听dist目录下文件的变化,此时浏览器会自动刷新
同时使用两个工具,操作麻烦。webpack打包不断将文件写入磁盘,browser-sync不断读磁盘内容。效率降低。
Webpack Dev Server
- 提供用于开发的HTTP Server
- 集成 自动编译 和 自动刷新浏览器 等功能
cnpm i webpack-dev-server --dev
为了提升效率,并没有将打包文件写入文件中,暂时存放在内存当中
npx webpack-dev-server
npx webpack-dev-server --open
自动唤起浏览器打开
- Dev Server 默认只会 serve 打包输出文件
只要是通过webpack输出的文件都可以被直接访问,其它静态资源文件也需要serve,需要额外去告诉webpack-dev-server
//webpack.config.js
module.exports = {
devServer:{
//contentBase属性指定额外的资源路径
//可以是字符串或者数组
contentBase:'./public'
},
}
Webpack Dev Server 代理API服务
devServer:{
//contentBase属性指定额外的资源路径
//可以是字符串或者数组
contentBase:'./public',
proxy:{
'/api':{
target:'https://XXXXXXXX',
pathRewrite:{
'^/api':''
},
//不使用 localhost:8080 作为请求的主机名 会以代理的地址作为请求地址
changeOrigin:true
}
}
},
webpack 配置 Source Map
webpack.config.js
devtool:'source-map'
devtool的各个模式
- eval-是否使用eval执行模块代码
- cheap-Source Map 是否包含行信息
- module-是否能够得到Loader处理之前的源代码
开发环境选择模式
cheap-module-eval-source-map
(代码经过Loader转换后差异较大,所以使用module。重写打包的速度较快)
生产模式
‘none’
不生成SourceMap
Source Map会暴露源代码
或者选择 nosources-source-map 出错误可以定位到位置 但是不会暴漏代码
Webpack自动刷新问题
自动刷新导致页面操作状态丢失
希望在页面不刷新的前提下,模块也可以及时更新
HMR (Hot Module Replacement)模块热替换
在应用程序运行的过程中实时替换某个模块,应用运行状态不受影响
热替换只将修改的模块实时替换至应用中,不必完全刷新页面
HMR集成在webpack-dev-server中
webpack-dev-server --hot 开启
也可以在配置文件中配置开启
//webpack.config.js
const webpack = require('webpack')
module.exports = {
devServer:{
hot:true
},
plugins:[
new webpack.HotModuleReplacementPlugin()
]
}
配置完成 通过 npx webpack-dev-server --open 启动服务,修改css可以实现热更新,但是修改js还是会刷新页面。
因为Webpack中的HMR并不可以开箱即用
Webpack中的HMR需要手动处理模块热替换逻辑
因为在style-loader中自动处理了样式的热更新,因为js导出的类型与使用是不确定的,所以没有任何规律,所以无法开箱即用一种方式去实现热更新。而css就是将样式文件替换就可以实现热更新。而在使用框架开发时,可以实现热更新,是因为在框架下开发,每种文件都是有规律的。通过脚手架创建的项目内部都集成了HMR方案。
//main.js
import createEditor from './editor'
import background from './better.png'
module.hot.accept('./editor', () => {})
//通过module.hot.accept手动处理热更新 1.依赖的模块 2.处理函数
//手动处理了 就不会自动刷新页面了
module.hot.accept('./better.png', () => {
img.src = background
console.log(background)
})
在手动处理时,如果处理中代码错误,会自动刷新页面。此时错误信息也就无法看到,为了解决这个问题可以配置
//webpack.config.js
module.exports = {
devServer:{
//hot:true
hotOnly:true
}
}
Webpack不同环境下的配置
- 配置文件根据环境不同导出不同的配置
//支持导出一个函数
//参数1.通过cli传递的一个环境参数 argv运行cli过程中所传递的所有参数
module.exports = (env,argv) => {
const config = {}//开发环境配置
//判断环境是否是生产环境
if(env === 'production'){
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),//添加生产环境所需额外插件
new CopyWebpackPlugin(['public'])
]
}
return config
}
-
一个环境对应一个配置文件
大型项目使用不同环境对应不同配置文件
一般创建三个文件 一个存放生产与开发环境的公共配置 一个为开发环境配置 一个为生产环境配置
图片.png
webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
})
]
}
webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
webpack.prod.js
const merge = require('webpack-merge') //webpack提供合并webpack 需要先安装
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
此时没有了默认的配置文件 运行时需要指定配置文件
npx webpack --config webpack.prod.js
yarn webpack --config webpack.prod.js
DefinePlugin
为代码注入全局成员
在production模式下默认启用,为代码注入 process.env.NODE_ENV 根据该成员判断当前运行环境
//webpack.config.js
const webpack = require('webpack')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin({
// 值要求的是一个代码片段
API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
}
Tree-shaking
去除代码中未引用的部分
在生产模式下自动开启
optimization:{
usedExports:true,//只导出用到的模块 标记
minimize:true//压缩代码 移除
}
合并模块
concatenateModules
optimization:{
concatenateModules:true,//将所有模块全部合并在一起 输出在一个函数中,提升运行效率 减少代码体积
usedExports:true,//只导出用到的模块 标记
minimize:true//压缩代码 移除
}
Tree-shaking & babel
Tree-shaking的实现前提是 ES Modules ,由Webpack打包的代码必须使用 ESM
sideEffects
通过配置标识代码是否有副作用,为Tree-shaking提供更大的压缩空间
副作用:模块执行时除了导出成员之外所作的事情
sideEffects 一般用于 npm 包标记是否有副作用
//webpack.config.js
optimization:{
sideEffects:true,//在production模式下自动开启
}
//package.json
"sideEffects":false //标识在代码中没有副作用
代码分包 Code Splitting
项目中所有代码最终都被打包在一起,模块复杂多 bundle体积就会很大,在应用工作时,并不是每个模块都要加载的。
分包,按需去加载模块
- 多入口打包 输出多个打包结果
- 动态导入 ESM实现模块按需加载
多入口打包
适用于传统的多页应用程序
一个页面对应一个打包入口,公共部分单独提取
//webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
//打包多个文件,将entry定义为一个对象
entry: {
index: './src/index.js',//文件名:文件路径
album: './src/album.js'
},
output: {
filename: '[name].bundle.js'//动态输出文件名称
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
//HtmlWebpackPlugin会输出一个自动引入所有打包结果的html
//要指定html所使用的文件 使用chunk 每个打包入口是一个独立的chunk
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
提取公共模块
在webpack.config.js配置optimization优化属性
optimization: {
splitChunks: {
// 自动提取所有公共模块到单独 bundle
chunks: 'all'
}
},
按需加载
需要用到某个模块时,再加载这个模块
- 动态导入
动态导入的模块会被自动分包
通过import()导入模块
魔法注释
默认通过动态导入的模块,打包后文件名称默认只是一个序号,如果需要给bundle命名,使用webpack特有的魔法注释实现。
相同名字的模块会被打包在一起。
import(/* webpackChunkName: 'magic' */'./posts/posts').then(({ default: posts }) => {
})
//打包后js
magic.bundle.js
MiniCssExtractPlugin
可以将css代码从打包结果当中提取出来的插件,可以实现css模块的按需加载
cnpm i mini-css-extract-plugin --dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
MiniCssExtractPlugin.loader,//通过MiniCssExtractPlugin.loader实现样式文件通过link标签引入
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin()
]
}
OptimizeCssAssetsWebpackPlugin
压缩输出的CSS文件
webpack自己的压缩只针对于js文件,其它文件压缩需要借助于其它的插件
cnpm i optimize-css-assets-webpack-plugin --dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin') //cnpm i terser-webpack-plugin --dev 手动添加内部js压缩
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
//使用minimizer,webpack默认为要自定义压缩,不再会实现默认js压缩
//在需要压缩的场景下才会触发压缩,如以生产模式打包 npx webpack --mode production。
minimizer: [
new TerserWebpackPlugin(),//手动添加内部js压缩
new OptimizeCssAssetsWebpackPlugin()
]
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
MiniCssExtractPlugin.loader,//通过MiniCssExtractPlugin.loader实现样式文件通过link标签引入
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin()
]
}
输出文件名Hash
生成前端文件都会启用服务器静态资源缓存,可以缓存文件,减少请求,提升响应速度。
应用重新发布更新,文件名不变,缓存不能及时响应更新。
在生产模式下,文件名使用Hash值,一旦资源发生改变,文件名称也随之改变。对于客户端而言,文件名称改变相当于不同的资源,会重新请求最新的资源文件。
- 针对整个项目的hash
output:{
filename:'[name]-[hash].bundle.js'
}
文件发生变,重新打包hash值都会发生变化
-
chunk hash
同一路打包的hash是一样的 ,同一个chunk
图片.png - contenthash
文件级别的hash,根据输出文件内容输出hash值,不同的文件就有不同的hash值.
解决缓存最好的方式,精确到了每一个文件
output: {
filename: '[name]-[contenthash].bundle.js' //指定hash长度 [contenthash:8]
},
Rollup
Rollup是一个打包工具,Rollup与webpack非常类似,相比与webpack更为小巧。
仅仅是一款 ESM 打包器,并没有其它额外的功能
Rollup中并不支持类似 HMR 这种高级特性
提供一个充分利用ESM各项特性的高效打包器
cnpm i rollup --dev
文件输入路径 及打包后文件格式 iife自调用函数格式
npx rollup ./src/index.js --format iife
将打包结果输出到文件中
npx rollup ./src/index.js --format iife --file dist/bundle.js
打包结果代码十分简洁,只会保留用到的部分,会默认开始treeshaking优化结果。
Rollup配置文件
创建rollup.config.js文件
rollup会额外处理该文件,所以可以直接使用esmodule格式
rollup.config.js
export default {
input: 'src/index.js',//打包入口文件路径
output: {
file: 'dist/bundle.js',//输出文件名
format: 'iife'//输出格式
}
}
默认不会使用配置文件 需要加上--config参数
npx rollup --config
或者指定不同配置文件名称,区分开发生产不同的配置文件
npx rollup --config rollup.config.js
Rollup插件
插件是Rollup唯一的扩展途径
cnpm i rollup-plugin-json --dev
import json from 'rollup-plugin-json'//返回调用函数
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife'
},
plugins: [
json() //将调用结果放入plugins数组中
]
}
Rollup加载npm模块
并不能像webpack一样直接导入模块名称的方式导入
rollup-plugin-node-resolve
该插件可以实现直接导入模块名称的方式导入
cnpm i rollup-plugin-node-resolve --dev
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()
]
}
//例如代码导入lodash-es
import _ from 'lodash-es'
Rollup 加载 CommonJs模块
rollup只涉及处理esmodule模块打包,CommonJs默认不被支持
可以使用 rollup-plugin-commonjs
cnpm i rollup-plugin-commonjs --dev
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()
]
}
此时就可以导入处理commonJs模块
Rollup代码分包
通过动态导入方式
import('./logger').then(({ log }) => {
log('code splitting~')
})
rollup.config.js
//此时输出为多个文件 需要使用dir属性
//通过AMD方式打包
export default {
input: 'src/index.js',
output: {
// file: 'dist/bundle.js',
// format: 'iife'
dir: 'dist',
format: 'amd'
}
}
npx rollup --config
Rollup多入口打包
export default {
// input: ['src/index.js', 'src/album.js'],
input: {
foo: 'src/index.js',
bar: 'src/album.js'
},
output: {
dir: 'dist',
format: 'amd'
}
}
打包后的js,页面不能直接去引用
<body>
<!-- AMD 标准格式的输出 bundle 不能直接引用 -->
<!-- <script src="foo.js"></script> -->
<!-- 需要 Require.js 这样的库 -->
<script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script>
</body>
Rollup与 Webpack
Rollup
优点
- 输出结果更加扁平
- 自动移除未引用代码
- 打包结果可读
缺点 - 加载非 ESM 的第三方模块比较复杂
- 模块最终被打包在一个函数中,无法实现 HMR
- 浏览器环境中,代码差分功能依赖 AMD 库
应用开发使用Webpack
库/框架开发 使用 Rollup
Parcel 零配置的前端应用打包器
cnpm init
cnpm i parcel-bundler --dev
创建打包入口文件 src/index.html
npx parcel src/index.html
通过parcel找到入口文件所引入的文件,进行打包
npx parcel build src/index.html
以生产模式进行打包
- 完全零配置,自动安装依赖等特点
- 构建速度快
ESLint
- 主流的js lint工具 监测JS代码质量
- 统一开发者编码风格
cnpm init -y
cnpm i eslint --save-dev
npx eslint .\file.js
使用eslint检查文件
要先将eslint初始化配置
npx eslint --init
.eslintrc.js文件
module.exports = {
"env": {//标记运行环境
"browser": true,//代码运行在浏览器环境中 可以使用document等成员变量
"es2021": true
},
//继承共享配置
"extends": [
"standard"
],
//设置语法解析器 是否允许es版本语法 如let const等
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
//添加规则 属性名是内置的规则名称 属性值有三种情况 off,warn,error
"rules": {
}
};
配置注释
//忽略代码行
const test1 = "${name} code" // eslint-disable-line
//忽略指定规则
const test2 = "${name} code" // eslint-disable-line no-template-curly-in-string
style-lint
对css代码进行校验检测
cnpm i stylelint -D
创建stylelintrc.js配置文件,配置与eslint基本相同
安装stylelint共享配置模块
cnpm i stylelint-config-standard
//stylelintrc.js
module.exports={
extends:"stylelint-config-standard"
}
npx stylelint ./index.css
检验指定css文件 可以通过--fix自动修复
对于sass文件检验
cnpm i stylelint-config-sass-guidelines -D
//stylelintrc.js
module.exports={
extends:[
"stylelint-config-standard",
"stylelint-config-sass-guidelines"
]
}
npx stylelint ./index.sass
Prettier 前端代码格式化工具
cnpm i prettier -D
npx prettier ./index.css
命令格式化代码 会直接输出在控制台当中
npx prettier ./index.css --write
会将格式化后的代码写入文件
npx prettier . --write
格式化所有文件
GitHooks
通过Git Hooks在代码提交前强制lint
- Git Hooks 是Git钩子,每个钩子都对应一个任务
-
通过shell脚本可以编写钩子任务触发时要具体执行的操作
在.git文件目录中
图片.png
这个钩子,当执行commit时都会触发这个钩子
Husky可以实现Git Hooks 的使用需求
在不使用shell脚本情况下,也可以实现使用Git Hooks钩子功能
cnpm i husky -D
//package.json
{
"scripts": {
"lint": "eslint ./index.js"
},
"husky":{
//配置钩子
"hooks":{
"pre-commit":"npm run lint"
}
}
}
git add ,
git commit -m "lint"
此时就会对代码进行lint检验,但是校验完后不会继续提交等其它操作。借助lint-staged
cnpm i lint-staged -d
//package.json
{
"scripts": {
"lint": "eslint ./index.js",
"precommit":"lint-staged"
},
"husky":{
//配置钩子
"hooks":{
"pre-commit":"npm run precommit"
}
},
"lint-staged":{
//定义多个执行任务
"*.js":[
"eslint",
"git add"
]
}
}
webpack一个配置模板
webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const utils = require('./utils.js')
module.exports = {
entry:utils.resolve('./src/main.js'),
output:{
path:utils.resolve('./dist'),
filename:'[name].[hash:6].js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'assets': utils.resolve('assets'),
'pages': utils.resolve('src/pages'),
'public': utils.resolve('public'),
'components': utils.resolve('src/components')
}
},
module: {
rules: [
{
test: /\.(js|vue)$/,
use: 'eslint-loader',
enforce: 'pre'
}
]
},
plugins: [
new HtmlWebpackPlugin({
title:"my vue",
filename: 'index.html',
template: 'src/index.html',
inject: true,
url:'public/'
}),
new VueLoaderPlugin()
]
}
webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.common.js')
const utils = require('./utils.js')
const PORT = 8080
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
clientLogLevel: 'warning',
hot: true,
port: PORT,
open: true,
contentBase:utils.resolve('./dist'),
publicPath:'/',
overlay: { warnings: false, errors: true },
},
module: {
rules: [
{
test: /\.css?$/,
use: ['vue-style-loader','css-loader']
},
{
test: /\.styl(us)?$/,
use: ['vue-style-loader','css-loader', 'stylus-loader']
},
{
test: /\.(js|vue)$/,
use: 'eslint-loader',
enforce: 'pre'
}, {
test: /\.less?$/,
use: [
'vue-style-loader',
'css-loader',
'less-loader'
]
} ,{
test: /\.vue$/,
use: 'vue-loader'
}, {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10*1024,
esModule: false,
}
}
}, {
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
}
}
}, {
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
}
}
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
webpack.prod.js
const merge = require('webpack-merge')
const baseConfig = require('./webpack.common')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const utils = require('./utils.js')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'none',
optimization: {
usedExports:true,
minimize:true,
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
}
}
}
},
module: {
rules: [
{
test: /\.css?$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.styl(us)?$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader']
},
{
test: /\.(js|vue)$/,
use: 'eslint-loader',
enforce: 'pre'
}, {
test: /\.less?$/,
use: [
'vue-style-loader',
'css-loader',
'less-loader'
]
} ,{
test: /\.vue$/,
use: 'vue-loader'
}, {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10*1024,
esModule: false,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}
}, {
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
}
}, {
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'main.css'
}),
new CopyWebpackPlugin({
patterns: [
{
from: utils.resolve('public/'),
to: utils.resolve('dist/public'),
toType: 'dir'
}
]
})
]
})
util.js
const path = require('path')
module.exports = {
resolve: function (dir) {
return path.join(__dirname, dir)
},
assetsPath: function (_path) {
const assetsSubDirectory = 'public'
return path.posix.join(assetsSubDirectory, _path)
}
}
资料来源:拉勾教育-前端训练营
网友评论