目录
5.Node 环境中,ES Modules 载入CommonJs模块
2.5 webpack dev server 自动编译和自动刷新浏览器
2.9 Tree-Shaking usedExports 去除未使用的代码
2.13 postcss 处理一些需要兼容的css(加浏览器前缀)
2.15 polyfill 填充 兼容更新的js promise等
2.17 MiniCssExtractPlugin做Css抽离和压缩
2.19 scope hoisting 作用域提升 减少作用域链的查找
2.20 compression-webpack-plugin资源压缩
2.12 speed-measure-webpack-plugin 打包时间和内容分析
一、ES Modules
1.基本特性
<script type="module"></script>
- ESM 默认使用严格模式 (无this)
- 每个Module都是运行在单独的私有作用域中
- ESM都是通过CORS的方式请求外部JS模块的 (浏览器要支持CORS)
- ESM的Script标签会延迟执行脚本 (同defer)
2.ES Module的导入导出
//导出成员是只读的
//导出的是成员的地址
//x.js
export let name = {}
import { name } from './x.js'
//a.js
let obj = {}
export default obj
import obj from './a.js'
import {} from './a.js' // 代表执行该模块
import { name } from 'http://xxx/xxx' // 可以从远程地址导入
import * as mod from './a.js' // 代表导入a.js中的所有模块
//import 不能从变量导入,如果需要动态导入
import('./mod.js').then(function(module){
console.log(module)
})
//导出默认成员和命名成员
import { name, default as title } from 'a.js'
//或
import title, { name } from 'a.js'
3.ES Modules 运行环境的兼容性问题
polyfill兼容方案,通过script引入
支持浏览器的会执行两遍
解决方案
<script nomodule src="xxxx"></script>
4.Node.js对ES Modules的支持情况
node 版本8以上
执行语句:node --experimental-modules index.mjs
import fs from 'fs' 引入系统内置模块
5.Node 环境中,ES Modules 载入CommonJs模块
- ES Modules中可以导入CommonJs
- CommonJs模块始终只会导出一个默认的成员,所以不能使用import { xx } from 'xxx'的方式导入CommonJs的内容 。即不能直接提取成员
- CommonJs不能导入ESModules(不能在CommonJs模块中通过require载入ES Modules)
二、Webpack
前端整体的模块化 不单单是JS
1.webpack命令
yarn init
yarn add webpack webpack-cli --dev
yarn webpack --version
yarn webpack
2.Webpack 配置文件
2.1 webpack.config.js
const path = require('path')
// 自动清除dist目录文件的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// webpack自动输出HTML文件
const HTMLWebpackPlugin = require('html-webpack-plugin')
// public下的文件拷贝到输出目录
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
mode: 'none', // 模式 development production none
entry: './src/index.js', // 入口
output: { // 输出文件路径
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
// 根目录
// publicPath: 'dist/'
},
// treeShaking去除冗余代码 生产环境不需要 会自动开启
optimization: {
usedExports: true, // 标记未使用代码 只导出被使用的成员
minimizer: true, // 去除未使用代码 压缩输出结果
concatenateModules: true, // 合并模块代码到同一个函数中
// sideEffects: true //去除模块副作用 没有用到的模块不会被打包() ['./src/s.js']
// minimizer: [
//压缩打包后的css文件
// new OptimizeCssAssetsWebpackPlugin()
// new TerserWebpackPlugin() // js 压缩
// ]
},
devtool: 'cheap-module-eval-source-map',
// webpack-dev-server
devserver: {
// 静态资源目录
contentBase: './public',
// 开启热更新
hot: true,
// hotonly: true, 热更新错误也不会刷新页面
// 代理
proxy: {
'/api': {
target: 'http://xx.com',
// 路径替换
pathRewrite: {
'^/api': '',
},
// 实际代理主机名
changeOrigin: true
}
}
},
// loader 加载任何类型的资源
module: {
rules: [
// css loader
{
test: /.css$/,
// use 从后往前执行
use: [
'style-loader',
'css-loader'
]
},
// 文件资源加载器
{
test: /.png$/,
use: 'file-loader' // 大文件使用
},
{
test: /.png$/,
use: {
loader: 'url-loader', // 'url-loader 将图片转变为base64 data urls (小文件使用 Data URLs)
options: {
limit: 10 * 1024 // 10KB
}
}
},
// es6语法转换babel-loader
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { modules: 'false'}]
]
}
}
},
// html-loader html文件中,img 的资源 a标签的资源
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'a:href']
}
}
}
]
},
// 插件 plugin
Plugins: [
// 自动清除插件
new CleanWebpackPlugin(),
// webpack自动输出HTML文件 生成index.html
new HTMLWebpackPlugin({
title: '标题',
meta: {
viewport: 'width=device-width'
},
// 模板路径
template: './src/index.html' // 文件中使用<%= htmlWebpackPlugin.options.title %> 传变量
}),
// 生成about.html
new HTMLWebpackPlugin({
filename: 'about.html'
}),
// 拷贝文件到dist目录 开发阶段不需要使用
new CopyWebpackPlugin([
'public'
]),
// HRM热更新
new webpack.HotModuleReplacementPlugin(),
// deginePlugin 全局注入变量 process.env.Node-env
new webpack.DefinePlugin({
API_BASE_URL: JSON.stringify('http://api.example.com') // 应该是代码片段
}),
// css模块打包成一个文件
// new MiniCssExtraPlugin(), // 使用此插件 不需要使用style-loader 替换为 MiniCssExtraPlugin.loader (link的方式注入样式文件)
]
}
2.2 loader分类
- 编译转换类 css-loader babel-loader style-loader sass/less-loader
- 文件操作类 file-loader url-loader
- 代码检查类 eslint-loader
2.3 webpack工作原理
递归查找依赖项 并进行打包
2.4 自定义loader和plugin
loader: 返回的必须是js
const marked = require('marked')
module.exports = source => {
//必须返回js代码
const html = marked(source);
return `module.exports = ${JSON.stringify(html)}`
}
plugin: 返回的必须是一个函数或包含apply方法
// 删除dist中的注释
class MyPlugin {
apply(compiler) {
// compiler 包含webpack的各种钩子
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation 此次打包的上下文
for(let name in compilation.assets) {
// 判断是否是js结尾的
if(name.endsWith('.js')) {
const contents = compilation.assets[name].source();
const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
2.5 webpack dev server 自动编译和自动刷新浏览器
// 打包结果暂存在内存中 提高效率
yarn add webpack-dev-server --D
yarn webpack server
// webpack.config.js
devserver: {
// 热更新
hot: true,
// 本地服务所在的目录 和output里的publicPath保持一致
// publicPath: '',
// 静态资源目录
contentBase: './public',
// 监控contentBase里内容的变化
watchContentBase: true,
// 代理
proxy: {
'/api': {
target: 'http://xx.com',
// 路径替换
pathRewrite: {
'^/api': '',
},
// 实际代理主机名
changeOrigin: true
}
}
},
2.6 Source Map 源代码地图
// webpack.config.js
devtool: 'source-map'
// eval 是否使用eval执行模块代码
// cheap 包含行信息
// module 是否能够得到loader处理之前的源代码
// devtool的取值
source-map
eval // 只能定位错误文件
eval-source-map // 可定位到具体行列
cheap-eval-source-map // 可定位到具体行, es6转换后的结果
cheap-module-eval-source-map // 可定位到具体行 源代码
cheap-source-map //loader 处理过后的代码 定位到行
inline-source-map // 定位到行列 最不常用 data-url的方式
nosources-source-map // 开发工具中看不见源代码 但是有行列信息 生产环境下常用
开发环境下常使用的模式
cheap-module-eval-source-map 首次打包慢 重写打包快
生产环境下
none 不生成打包map
2.7 模块热更新 Webpack HMR
避免刷新后状态丢失问题
在webpack.config.js中开启:
const webpack = require('webpack')
module.exports = {
devserver: {
// 开启热更新
hot: true,
},
Plugins: [
// HMR热更新
new webpack.HotModuleReplacementPlugin()
]
}
以上只能达到css的热替换
js png的热替换需要自己定义 module.hot.accept('文件', () => {处理逻辑})
2.8 webpack 不同环境下的配置
yarn webpack 开发环境命令
yarn webpack --env production 生产环境命令
小型项目 通过判断env的值 判断是哪个环境
const path = require('path')
// 自动清除dist目录文件的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// webpack自动输出HTML文件
const HTMLWebpackPlugin = require('html-webpack-plugin')
// public下的文件拷贝到输出目录
const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
module.exports = (env, argv) => {
const config = {
mode: 'none', // 模式 development production none
entry: './src/index.js', // 入口
output: { // 输出文件路径
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
// 根目录
// publicPath: 'dist/'
},
devtool: 'cheap-module-eval-source-map',
// webpack-dev-server
devserver: {
// 静态资源目录
contentBase: './public',
// 开启热更新
hot: true,
// hotonly: true, 热更新错误也不会刷新页面
// 代理
proxy: {
'/api': {
target: 'http://xx.com',
// 路径替换
pathRewrite: {
'^/api': '',
},
// 实际代理主机名
changeOrigin: true
}
}
},
// loader 加载任何类型的资源
module: {
rules: [
// css loader
{
test: /.css$/,
// use 从后往前执行
use: [
'style-loader',
'css-loader'
]
},
// 文件资源加载器
{
test: /.png$/,
use: 'file-loader' // 大文件使用
},
{
test: /.png$/,
use: {
loader: 'url-loader', // 'url-loader 将图片转变为base64 data urls (小文件使用 Data URLs)
options: {
limit: 10 * 1024 // 10KB
}
}
},
// es6语法转换babel-loader
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
// html-loader html文件中,img 的资源 a标签的资源
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'a:href']
}
}
}
]
},
// 插件 plugin
Plugins: [
// 自动清除插件
new CleanWebpackPlugin(),
// webpack自动输出HTML文件 生成index.html
new HTMLWebpackPlugin({
title: '标题',
meta: {
viewport: 'width=device-width'
},
// 模板路径
template: './src/index.html' // 文件中使用<%= htmlWebpackPlugin.options.title %> 传变量
}),
// 生成about.html
new HTMLWebpackPlugin({
filename: 'about.html'
}),
// 拷贝文件到dist目录 开发阶段不需要使用
new CopyWebpackPlugin([
'public'
]),
// HRM热更新
new webpack.HotModuleReplacementPlugin()
]
}
if (env === 'production') {
config.mode = 'production';
config.devtool = false;
config.Plugins = [
...config.Plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
}
return config;
}
大型项目可以使用不同环境使用不同的配置文件
webpack.dev.js webpack.prod.js webpack.common.js
yarn add webpack-merge -D 合并webpack配置的插件
const common = require('./webpack.common.js')
const merge = require('webpack-merge)
module.exports = merge(common, {
...
})
运行配置文件需要使用 yarn webpack --config webpack.prod.js
2.9 Tree-Shaking usedExports 去除未使用的代码
生产环境会自动开启
开发环境下开启方式
// treeShaking去除冗余代码 生产环境不需要 会自动开启
optimization: {
usedExports: true, // 标记未使用代码
minimize: true, // 去除未使用代码
// 去除未使用模块(export 导出的模块) 副作用清除
sideEffects: true
},
Tree Shaking 只能转换ESModules模式的代码
@babel/preset-env 会将ESModule转换成commonJS,所以在配置时 要禁止转化
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { modules: 'false'}]
]
}
}
}
2.10 webpack 代码分割
分包 按需加载
方式: 1.多入口打包 多页面程序
entry: { index: 'xxx', album: 'xxx'},
output: { filename: '[name].bundle.js'}
new HtmlWebpackPlugin({filename: 'index.html', chunks: ['index']})
公共模块的提取
optimization: {
splitChunks: {
chunks: 'all'
}
},
2.esModules动态导入
动态导入的模块会自动分包 生成自己的bundle
import('xxx').then((default: xxx) => {})
自动分包的模块命名
import(/*webpackChunkName: 'name'*/'xxx').then(() => {})
2.11 文件名hash
filename: '[name]-[hash:8].bundle.js'
// hash 整个文件hash值一样
// chunkhash 模块hash值
//contenthash 文件hash 解决缓存问题
2.12 browserslistrc 平台支持(兼容)
在根目录下创建 .browserslistrc文件 ,设置需要支持的平台
">1%",
"last 2 version",
"not dead"
在package.json里配置
"browserslist": [
">1%",
"last 2 version",
"not dead"
]
2.13 postcss 处理一些需要兼容的css(加浏览器前缀)
// 通过命令行运行
yarn add postcss postcss-cli autoprefixer -D
postcss-cli 命令
yarn postcss --use autoprefixer -0 ret.css ./src/css/tsetcss
//在webpack的配置项中使用
yarn add postcss-loader postcss-preset-env-D
// postcss-preset-env 各种postcss 插件的集合 加前缀 颜色8位 rgba等
{
test: /\.css$/,
use: ['style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1, esModule: false} // 当在css文件中发现@import导入的css需要进行前缀添加,后退一步 esModule处理url('xxx')
},
{
loader: 'postcss-loader',
options: { postcssOptions: { plugins: [require('postcss-preset-env')]}}
}]
}
// options中的预设也可以单独设置文件
//postcss.config.js
module.exports = {
plugins: [require('postcss-preset-env')]
}
会自动的在css前添加前缀
2.14 asset 处理图片,字体资源
之前是使用 url-loader file-loader 现在可使用asste代替
// 1 asset/resource -> file-loader
// 2 asste/inline -> url-loader
// 3 asste/source -> raw-loader
// 4 asset 做limit限制
//output: {
// assetModuleFilename: 'img/[name].[hash.6][ext]'
// },
// module
{
test: /\.(png|svg|gif|jpg?g)$/,
type: 'asset/resource',
generator: {
finename: 'img/[name].[hash.6][ext]'
}
}
{
test: /\.(png|svg|gif|jpg?g)$/,
type: 'asset/inline',
}
{
test: /\.(png|svg|gif|jpg?g)$/,
type: 'asset',
generator: {
finename: 'img/[name].[hash.6][ext]'
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024
}
}
}
//处理字体
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
finename: 'img/[name].[hash.6][ext]'
}
}
2.15 polyfill 填充 兼容更新的js promise等
preset-env可能不能完全的转换js polyfill打补丁 转换未转换的js
yarn add core-js regenerator-runtime
// babel.config.js
// useBuiltIns: false 不对当前的JS处理做polyfill填充
// useBuiltIns: usage, corejs: 3 依据用户源代码中所使用的新语法进行填充
// useBuiltIns: entry 根据当前筛选出的浏览器做填充
module.exports = {
presets: [
['@babel.preset-env', { useBuiltIns: false }]
]
}
2.16 resolve文件名称解析
默认情况下引入路径需要将后缀名写全,在这里配置后,就不需要带后缀
resolve: {
extensions: [".js", ".json", ".ts", ".vue"], // 自动补全这些后缀名
alias: {
'@': path.resolve(__dirname, 'src') // 别名
}
},
2.17 MiniCssExtractPlugin做Css抽离和压缩
一般在生产环境使用
const MiniCssExtractPlugin = require('minicss-extract-plugin')
//plugins
new MiniCssExtractPlugin({
filename: '[name].[hash:8].css'
})
//loader: css
MiniCssExtractPlugin.loader 替换style-loader
2.18 TerserPlugin 压缩TS
webpack5不需要单独安装 webpack4需要单独安装
optimization: {
minimizer: {
new TerserPlugin();
}
}
2.19 scope hoisting 作用域提升 减少作用域链的查找
webpack内置的插件
new webpack.optimize.ModuleConcatenationPlugin();
2.20 compression-webpack-plugin资源压缩
服务器会返回给前端压缩后的文件内容
const CompressionPlugin = require('compression-webpack-plugin')
new CompressionPlugin({
test: /\.(css|js)$/,
//(压缩前/压缩后)
miniRatio: 0.8, // 一般默认即可
// 压缩算法
algorithm: 'gzip'
});
2.12 speed-measure-webpack-plugin 打包时间和内容分析
const speedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new speedMeasurePlugin();
const webpackConfig = smp.wrap({
...
})