偶然的机会,读到苏轼了《赤壁赋》。和高中读来完全不一样,也许是上了年纪,饱尝了几年打工人的辛酸,忽然折服于苏轼的豁达,有种醍醐灌顶的舒畅,粘贴一些名句,与君共勉。
逝者如斯,而未尝往也;盈虚者如彼,而卒莫消长也。盖将自其变者而观之,则天地曾不能以一瞬;自其不变者而观之,则物与我皆无尽也,而又何羡乎!且夫天地之间,物各有主,苟非吾之所有,虽一毫而莫取。惟江上之清风,与山间之明月,耳得之而为声,目遇之而成色,取之无禁,用之不竭。是造物者之无尽藏也,而吾与子之所共适。 ----- 《赤壁赋》
-------------------------------------我是一条分割线--------------------------------------
模块化
- 提高工作效率,降低维护成本
一、模块化演变
- 文件划分方式(script中全局引入)
- 污染全局作用局
- 命名冲突
- 模块之间的依赖关系无法解决
- 命名空间方式(全局引入不同的命名空间)
- IIFE(立即执行函数)
- 确保私有空间的安全
二、模块化规范
CommonJS规范以同步模式加载模块()
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过modele.exports导出成员
- 通过require函数载入模块 AMD(异步模块定义规范)--Require.js node中遵循CommonJS,浏览器中遵循ES Modules规范
三、ES Modules(最主流的前端规范标准)
通过给script标签添加 type = model 的属性,就可以以ES Module的标准执行其中的JS代码
1、基本特性
- ESM自动采用严格模式,忽略‘use strict’
- 每个ES Module都运行在自己的私有作用域中
- ESM是通过CORS的方式进行外部JS请求(需要请求的环境支持跨域)
- ESM的script的标签会延迟执行脚本
2、ES Module的导入和导出
//index.js 导出
var name = '11
export {name}
export default {name}
//导入
import {name} from "index.js"
console.log(name) //'11'
import {default as data} from "index.js"
console.log(data.name)
- export 后接{}导出为固定的语法,并不是指导出一个对象,引用的时候指向的是内存空间的地址
- 导出的是只读对象 ,不能修改
3、ES Modules in Node.js
- ES Module中可以导入CommonJS模块
- CommonJS不能导入ES Module模块
- CommonJS始终只会导出一个默认成员
- 注意import不是解构导出对象,
//module.mjs
var foo = '2',
bar = '55'
export { foo, bar }
//-----------------------------------------------------------------------------------------
//common.js,始终只会导出一个默认的成员
module.exports = {
foo: 'common js',
}
exports.foo = 'commonjs2' exports是module.exports的一个别名
//---------------------------------------------------------------------------------------
//index.mjs
import { foo, bar } from './module.mjs'
console.log(foo, bar)
import fs from 'fs'
fs.writeFileSync('./foo,txt', 'es modlue working')
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', '111222')
//在esm中引入commonJS模块
import mod from './common.js'
console.log(mod)
4、ES Modlue 与CommonJS的差异
- ESM中没有CommonJS中的那些模块成员了(require、module、exports、__filename、__dirname)
- 在package.json中添加 type:'modlue' 属性,就可以在js文件中直接使用ESM,而不是要在mjs文件中。此时CommonJS文件需要更名为 .cjs 文件
- 运行ESM文件node --experimental-modules index.js
//ESM中的文件路径
import {fileURLToPath} from 'url'
import {dirname, firname} from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
四、Webpack打包
1、Loader
默认入口文件为:src/index.js,输入路径为dist/main.js
Loader是webpack的核心特质,借助于Loader就可以加载任何类型的资源
loader负责资源文件从输入到输出的转换,同一个资源文件可以依次依赖多个Loader
因为模块打包的需要,所以处理import和export,并不能去转换代码中的其他es6特性。 webpack加载器分类
- 编译转换类
- 文件操作类
- 代码检查类
//webpack.config.js
const path = require('path')
module.exports = {
//yarn webpack --mode development 以开发模式运行
//yarn webpack --mode none 以最原始的状态打包带包
mode: 'none',
entry: './src/main.css', //自定义入口文件
output: {
filename: 'index.js', //输出的文件名称
path: path.join(__dirname, 'dist'), //输出的文件夹名称,路径为绝对路径
},
//yarn add css-loader --dev 处理css模块
//yarn add style-loader --dev 将css-loader处理后的结果以style标签的方式追加到页面上
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader'], //当配置了多个loader,执行的时候是从后往前执行
},
//yarn add file-loader --dev 文件加载器
//yarn add url-loader --dev 可以将图片转为base64
//小文件使用Data URLs,减少请求的次数
//大文件单独提取存放,提高加载速度
{
test: /.jpg$/,
// use: 'file-loader',
use: {
loader: 'url-loader',
Options: {
limit: 10 * 1024, //10kb以下的会处理为base64
},
},
},
],
},
}
yarn add babel-loader @babel/core @babel/preset-env --dev
webpack只是打包工具
加载器可以用来编译转化代码
module: {
rules: [
{
test: /.js$/,
use: {
//代替默认的加载器,可以处理js的新特性
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
]
}
webpack模块加载的方式
- 遵循ES Module标准的import声明
- 遵循CommonJS标准的require函数
- 遵循AMD标准的define函数和require函数
- 样式代码中的@import指令和url函数
- HTML代码中图片标签的src属性
2、Plugin
自动清除输出目录
yarn add clean-webpack-plugin html-webpack-plugin copy-webpack-plugin --dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/',
},
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /.css$/,
use: ['style-loader', 'css-loader'], //当配置了多个loader,执行的时候是从后往前执行
},
{
test: /.jpg$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024,
},
},
},
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'a:href'],
},
},
},
],
},
//c插件
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', //指定输出的文件名
template: './src/index.html',
}),
//打包的时候将public目录下文件直接拷贝
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, 'public'),//需要复制的文件夹
to: 'static',//复制到dist下的文件的名称
},
],
}),
],
}
3、插件的开发
plugin必须是一个函数或者是一个包含apply方法的对象 通过在声明周期的狗子中挂载函数实现扩展
//定义一个删除bundle.js的注释
class MyPlugin {
apply(compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', (compiliation) => {
//compiliation =>可以理解为此次打包的上下文
console.log()
for (const name in compiliation.assets) {
// console.log(name)
// console.log(compiliation.assets[name].source())
if (name.endsWith('.js')) {
const contents = compiliation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
compiliation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length,
}
}
}
})
}
}
4、增强webpack的开发体验
watch工作模式:yarn webpack --watch 浏览器自动刷新:brower-sync
5、Webpack Dev Server
- 集成自动编译和自动刷新浏览器等功能
- 静态资源需要额外配置contentBase
- 设置代理API解决跨域问题
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': '',
},
//不能使用locahost:8080作为请求Github的主机名
changeOrigin: true,
},
},
}
6、SourceMap(源代码地图)
解决了源代码与运行代码不一致所产生的问题
7、devtool模式对比
- eval-是否使用eval执行模块代码
- cheap-Source Map是否包含行信息
- module-是否能够得到Loader处理前的源代码
先择合适的Source Map
- 开发模式:cheap-module-eval-source-map
- 每行代码不超过80个字符
- 经过Loader转换过后的代码的差异较大
- 首次打包速度慢一些,重写打包速度较快
- 生产环境:none(nosources-source-map)
- Source Map会暴露源代码
- 调试是开发阶段的事情
7、模块热更新(HMR)
webpack在页面不刷新的情况下及时更新代码块 HMR是webpack中最强大的功能之一
- Webpack中的HMR需要手动处理模块热替换逻辑
- 样式文件可以开箱即用,因为样式文件是经过Loader处理的
- 通过脚手架创建的项目内部都集成了HMR方案
//webpack.config.js
const webpack = require('webpack')
devServer: {
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
//手动设置js热替换
//main.js ./heading需要监听的模块
module.hot.accept('./heading', () => {
console.log('heading模块更新了,需要收手动处理替换热更新逻辑')
})
8、生产环境优化
- 配置文件根据环境不同导出不同的配置
- 一个环境对应一个配置文件
//webpack.config.js
module.exports = (env, argv) => {
const config = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'js/bundle.js',
},
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public',
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
tets: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]',
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/inedx.html',
}),
new webpack.HotModuleReplacementPlugin(),
],
}
if (env === 'production') {
config.mode = 'production'
config.devtool = false
config.plugins = {
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
}
}
return config
}
yarn webpack --dev production
9、不通环境对应不同配置文件
//webpack.prod.js
//执行 yarn webpack --config webpack.prod.js
//或者将命令添加到scripts
const common = reuqire('./webpack.common.js') //公共配置
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = requre('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(common, {
mode: 'production',
plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(['public'])],
})
10、Tree-Shaking
- 会自动移除掉代码中未被引用的代码,会在生产模式下自动启用。
- Tree Shaking要使用ES Module
- babel的某些功能会将ES Module转换为CommonJS,此时Tree Shaking就会失效
//手动开启
optimization:{
usedExports:true,//指导出被引用部分的代码
concatenateModules:true,//尽可能把所有模块合并到一个函数中导出,提升运行效率,减少代码体积
minimize:true //压缩结果输出
},
11、多入口打包
entrry:{
index:'./src/main.js',
album:'./src/ablum.js'
},
optimization:{
splitChunks:{
chunks:'all'//将所有的公共模块提取到单独的bundle中
}
},
plugins: [
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/inedx.html',
filname:'index.html',
chunks:['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filname:'album.html',
chunks:['album']
}),
]
12、hash值
- hash:整个项目级别的(每个文件都有相同的hash值)
- chunkhash:某路入口文件的hash值全部发生变换(每路chunk有相同的hash值)
- contenthash:某个文件发生变化,打包后的hash值才会发生变化