模块化规范
1.单例设计模式
解决问题:模块间相互冲突的问题「闭包」 ; 模块间相互引用的问题;
不足之处:需要手动分析模块之间的依赖关系,按顺序依次导入相关模块;所有模块都是基于script一个个导入的,这样页面HTTP请求变多!使用闭包+命名空间
A:
let AModule = (function () { let name = "A"; const sum = function sum(...params) { let len = params.length, total = 0; if (len === 0) return 0; if (len === 1) return params[0]; params.forEach(item => { total += item; }); return total; }; return { sum }; })();
B:
let BModule = (function () { let name = "B"; // 在average获取平均数的方法中,我们需要用到A模块中的求和方法 const average = function average(...params) { let len = params.length, total = 0; if (len === 0) return 0; total = AModule.sum(...params); return total / len; }; return { average }; })();
Main:
/* 在MAIN中,我们需要用到A/B模块中的方法 */ console.log(AModule.sum(10, 20, 30, 40)); console.log(BModule.average(10, 20, 30, 40));
入口Index:
<script src="A.js"></script> <script src="B.js"></script> <script src="main.js"></script>
2.AMD(require.js )
RequireJS
优势:在保证模块之间独立和可以相互访问的基础上,HTML中无需再导入各个模块了「不存在顺序问题」,也不需要自己去分析相互间的依赖关系!
不足:依赖模块的导入是“前置导入”,只有把依赖模块动态导入完毕,才会触发回调函数执行「阻碍代码执行速度」;代码书写的顺序也不是很方便;可能存在重复导入;
Main:
这里引入 lib目录下的A和B
/* 全局配置 */ require.config({ // 声明后期所有模块的导入,都在lib目录下找 baseUrl: './lib' }); require(['B', 'A'], function (B, A) { console.log(A.sum(10, 20, 30, 40)); console.log(B.average(10, 20, 30, 40)); });
lib:
A:
/* define([依赖的模块],function(用形参接收依赖的模块){ 写本模块中的代码 return { 把本模块中需要供外部调用的方法导出 }; }) */ define(function () { let name = "A"; const sum = function sum(...params) { let len = params.length, total = 0; if (len === 0) return 0; if (len === 1) return params[0]; params.forEach(item => { total += item; }); return total; }; // 导出模块方法 return { sum }; });
B:
// 定义B模块,但是需要用到A模块中的方法,所以最开始设置一下依赖 // 当后期加载B模块的时候,先把A模块导入「去lib下找A.js」,确保A模块导入后,再把后面的函数执行 define(['A'], function (A) { // A存储的就是A模块中导出的方法 -> {sum:function...} let name = "B"; const average = function average(...params) { let len = params.length, total = 0; if (len === 0) return 0; total = A.sum(...params); return total / len; }; // 模块导出 return { average }; });
入口Index:
<!-- IMPORT JS 导入require.min.js(AMD思想)的时候,在全局提供两个函数: + require 导入模块 + define 定义模块 --> <script src="require.min.js"></script> <script src="main.js"></script>
3.Common.js规范
唯一的问题:浏览器端不支持CommonJS规范
淘宝“玉伯”仿照CommonJS规范,研发了一款插件 sea.js ,旨在把CommonJS规范搬到浏览器端运行「这种模块思想被称之为CMD」
- exports
- module.exports
- require
main:
/* CommonJS模块规范: 导出模块 module.exports = { ... } module.exports = ... 导入模块 const xxx = require(模块地址「可以省略.js后缀」) 实现了真正的按需导入:啥时候用,啥时候导入即可 CommonJS模块规范实现了模块导入的缓存机制 导入一个模块,会把这个模块中的代码执行,获取其导出的内容「并且缓存起来」 当后续,在遇到这个模块的导入,不再重新把模块中的代码执行,而是直接获取之前缓存中存储的导出的内容!! 只支持在Node(或者webpack)环境下运行,不支持浏览器环境!! */ const A = require('./A'); console.log(A.sum(10, 20, 30, 40)); const average = require('./B'); console.log(average(10, 20, 30, 40));
A:
let name = "A"; const sum = function sum(...params) { let len = params.length, total = 0; if (len === 0) return 0; if (len === 1) return params[0]; params.forEach(item => { total += item; }); return total; }; /* 模块的导出 */ module.exports = { sum };
B:
let name = "B"; /* 模块的导入:把模块导出的内容获取到,赋值给A */ const A = require('./A'); const average = function average(...params) { let len = params.length, total = 0; if (len === 0) return 0; total = A.sum(...params); return total / len; }; /* 模块导出 */ module.exports = average;
入口index:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=\, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="main.js"></script> </body> </html>
4.ES6Module模块规范
ES6 入门教程
可以直接运行在浏览器端;不支持IE;
- export
- export default
- import
- import from
A:
let name = "A"; const sum = function sum(...params) { let len = params.length, total = 0; if (len === 0) return 0; if (len === 1) return params[0]; params.forEach(item => { total += item; }); return total; }; /* 模块导出 */ export default sum; //Module对象{ default:sum函数 } //===================== /* 模块导出:export 或者 export default 无论基于何种方式,模块导出的永远是一个“Module对象”!! 第一种方式:export + 一个模块中可以使用多次,分别导出多项内容 + 导出的每一项内容,都是给“Module对象”设置相关的成员 第二种方式:export default + 一个模块中只能用一次 + 它是给“Module对象”设置一个叫做default的成员,成员值是导出的内容 */ /* // 正确语法:导出一个变量/值/创建函数表达式/对象... // export default sum; // export default 10; // export default function fn() { }; // export default { // x: 10, // name // }; // 错误语法 // export default let age = 12; */ /* // 语法:不能直接导出一个变量/值,必须在声明的时候“同时导出” // export name; //错误的写法 export let age = 14; //声明一个变量并且导出「Module对象.age=14」 export function fn() { }; export const obj = {}; // 语法:可以导出一个对象(或代码块),其中包含多个我们需要导出的内容 export { name, //正确语法:前提name是存在的,我们把name赋值给Module对象,值就是name变量的值 // x:10 //错误语法:不能直接在这用键值对方式赋值 }; */
B:
/* 模块导入:sum => Module对象.default */ import sum from './A.js'; let name = "B"; const average = function average(...params) { let len = params.length, total = 0; if (len === 0) return 0; total = sum(...params); return total / len; }; /* 模块导出 */ export default average; //Module对象{ default:average函数 }
main:
import sum from "./A.js"; import average from "./B.js"; console.log(sum(10, 20, 30, 40)); console.log(average(10, 20, 30, 40)); /* 模块导入:把模块导出的“Module对象”中的每一项内容拿到 => import 导入模块的地址:相对地址、不能省略后缀名「后期在weblack中可以省略」 语法一: import xxx from './A.js' 不是把“Module对象”整体导入进来赋值给xxx,而是只拿到了“Module对象.default”属性值「xxx=Module对象.default」「换句话说,基于export default xxx导出的内容,用这种方式直接导入」 语法二: import {x,y} from './A.js' 用解构赋值的方式获取导出的内容,首先不是把“Module对象.default属性值”进行解构赋值;而是直接给“Module对象”解构赋值「换句话来讲,它是获取基于 export let xxx=xxx 这种方式导出的内容」 语法三: import * as A from './A.js' 把模块导出的“Module对象”中的所有内容都拿到,最后赋值给A「A=Module对象」 */ /* import A from './A.js'; console.log(A); import obj from './xxx.js'; //obj->0x001 import { x, m } from './xxx.js'; //不是把default导出的对象先获取,然后解构赋值 // x-> Module.x -> undefined // m-> Module.m -> 100 import * as A from './xxx.js' // A.default -> 0x001 // A.m -> 100 export default { x: 10, y: 20 }; //对象0x001 => Module{ default:0x001 } export let m = 100; //=> Module{ default:0x001,m:100 } */
入口index:
<!-- IMPORT JS + type="module" 是让JS支持ES6Module模块规范 + 预览要基于标准的HTTP协议(LiveServer),不能使用File文件协议 --> <script type="module" src="main.js"></script>
2.Webpack的基础操作
安装
// 为防止全局安装webpack导致版本冲突,真实项目中以本地安装为主 $ npm init -y $ npm install webpack webpack-cli --save-dev OR $ yarn add webpack webpack-cli -D
零配置使用
/* * 默认会打包SRC目录中的JS文件(入口默认index.js) * 打包完成的目录默认是DIST/MAIN.JS * webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包 */ $ npx webpack
自定义基础配置
webpack.config.js
// Node内置的路径处理模块 const path = require('path'); // 导出自定义配置项 module.exports = { // 环境模式「生产环境:production 开发环境:development」 mode: 'production', // 打包入口「相对路径」 entry: './src/index.js', // 打包出口 output: { // 生成的文件名. [hash]创建随机哈希值 filename: 'bundle.[hash].js', // 打包地址「绝对路径」 path: path.resolve(__dirname, 'dist') } };
html-webpack-plugin
https://www.webpackjs.com/plugins/html-webpack-plugin/ $ yarn add html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { ... plugins: [ new HtmlWebpackPlugin({ // 指定视图模板 template: './public/index.html', // 编译后视图的名字 filename: 'index.html', // 是否压缩 minify: true }) ] }
多入口 & 多出口
const HtmlWebpackPlugin = require('html-webpack-plugin'); // HtmlWebpackPlugin需要配置多套 const htmlPlugins = ['index', 'login'].map(chunk => { return new HtmlWebpackPlugin({ template: `./public/${chunk}.html`, filename: `${chunk}.html`, // 指定导入的JS chunks: [chunk], minify: true }); }); module.exports = { mode: 'production', // 配置多入口 entry: { index: "./src/index.js", login: "./src/login.js", }, // 出口采用相同的规则 output: { filename: "[name].[hash].js", path: path.resolve(__dirname, "dist") }, plugins: [ ...htmlPlugins ] };
clean-webpack-plugin
yarn add clean-webpack-plugin -
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { ... plugins: [ new CleanWebpackPlugin() ] };
webpack-dev-server
https://webpack.js.org/configuration/dev-server/ $ yarn add webpack-dev-server -D
/* paclage.json */ "scripts": { "start": "webpack server", "build": "webpack" } /* webpack.config.js */ module.exports = { ... devServer: { // 域名 host: '127.0.0.1', // 断口号 port: 3000, // GZIP压缩 compress: true, // 自动打开浏览器 open: true, // 热更新 hot: true, // 跨域代理 proxy: { "/jian": { target: "https://www.jianshu.com/asimov", changeOrigin: true, ws: true, pathRewrite: { "^/jian": "" } }, "/zhi": { target: "https://news-at.zhihu.com/api/4", changeOrigin: true, ws: true, pathRewrite: { "^/zhi": "" } } } } }; /* 测试接口 简书: https://www.jianshu.com/asimov/subscriptions/recommended_collections 知乎: https://news-at.zhihu.com/api/4/news/latest */
处理样式的loader「加载器」
$ yarn add css-loader style-loader less less-loader autoprefixer postcss-loader -D
module.exports = { ... /* 配置模块加载器LOADER,执行顺序:从右向左、从下向上 */ module: { rules: [{ test: /\.(css|less)$/, // 基于正则匹配哪些模块需要处理 use: [ "style-loader", // 把CSS插入到HEAD中 "css-loader", // 编译解析@import/URL()这种语法 "postcss-loader", // 设置前缀 "less-loader" // 把LESS编译为CSS ] }] } };
postcss.config.js
module.exports = { plugins: [ require('autoprefixer') ] }; // 或者在配置项中直接处理 { loader: "postcss-loader", options: { postcssOptions: { plugins: [ require('autoprefixer') ] } } },
package.json OR .browserslistrc
// https://github.com/browserslist/browserslist "browserslist": [ "> 1%", "last 2 versions", "not dead" ]
mini-css-extract-plugin 抽离CSS样式 /ˈekstrækt/
https://www.npmjs.com/package/mini-css-extract-plugin $ yarn add mini-css-extract-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { ... plugins: [ ... new MiniCssExtractPlugin({ // 设置编译后的文件名字 filename: 'main.[hash].css' }) ], ... module: { rules: [{ test: /\.(css|less)$/, use: [ // 使用插件中的LOADER代替STYLE方式 MiniCssExtractPlugin.loader, ... ] }] } };
基于babel实现ES6的转换
$ yarn add babel babel-loader @babel/preset-env @babel/core -D $ yarn add @babel/polyfill
module.exports = { ... module: { rules: [{ test: /\.js$/, use: ['babel-loader'], // 设置编译时忽略的文件和指定编译目录 include: path.resolve(__dirname, 'src'), exclude: /node_modules/ }] } };
babel.config.js
module.exports = { presets: [ "@babel/preset-env" ] };
index.js
import '@babel/polyfill';
设置优化项:压缩CSS/JS
$ yarn add css-minimizer-webpack-plugin terser-webpack-plugin -D
const TerserPlugin = require('terser-webpack-plugin'); const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin'); module.exports = { optimization: { // 设置压缩方式 minimizer: [ new CssMinimizerWebpackPlugin(), new TerserPlugin() ] } };
设置解析器:配置别名
module.exports = { resolve: { alias: { '@': path.resolve(__dirname, './src') } } };
图片的处理
$ yarn add file-loader url-loader -D
module.exports = { ... module: { rules: [ ... { test: /\.(png|jpe?g|gif)$/i, type: 'javascript/auto', use: [{ // 把指定大小内的图片BASE64 loader: 'url-loader', options: { limit: 200 * 1024, esModule: false, name: 'images/[name].[hash].[ext]' } }] }] }, // 设置打包的最大资源大小 performance: { maxAssetSize: 100 * 1024 * 1024 * 1024, maxEntrypointSize: 100 * 1024 * 1024 * 1024 } };
笔记
webpack打包的原理:
webpack其实是一个平台,在平台中,我们会安装/融入/配置各种打包规则
+ mode:打包模式「开发环境development、生产环境production」
+ entry:入口「webpack就是从入口开始,根据CommonJS/ES6Module模块规范,分析出模块之间的依赖,从而按照相关的依赖关系,进行打包的」
+ output:出口
+ loader:加载器「一般都是用于实现代码编译的,但是想编译啥代码,我们需要安装对应的加载器,并且完成相关的规则配置 https://webpack.docschina.org/loaders/」
+ plugin:插件「处理的需求比较多了,例如:压缩、编译HTML、清空打包... https://webpack.docschina.org/plugins/」
+ resolve:解析器
+ optimization:优化项
+ devServer:配合webpack-dev-server,在本地启动Web服务,实现项目预览以及跨域处理...
+ ......
webpack是基于Node.js进行打包的!!把打包后的内容,最后都是交给浏览器去访问的!!
+ 想用webpack,电脑上需要安装Node
+ webpack中,支持CommonJS模块规范「我们写的配置规则,就是基于CommonJS规范处理的」
+ webpack中,支持ES6Module模块规范
+ 而且两种规范在webpack中可以混淆使用「webpack内部做了处理」wepack支持零配置打包:不需要自己写任何的配置规则,直接使用内置的默认规则进行打包
@1 安装webpack
$ yarn add webpack webpack-cli -D
安装在本地的模块,不能直接使用命令,我们还需要配置可执行脚本命令 => package.json「前提是 node_modules/.bin目录下,需要有这样的命令文件,才说明安装在本地的模块可以使用命令」
"scripts": {
"start": "webpack"
},
执行脚本命令
$ yarn start -> $ webpack
@2 默认去找 src/index.js,把其作为打包的入口,进行打包!!
打包后的内容输出到 dist/main.js 中!!
-----
以上两步操作,直接基于 $ npx webpack 处理也是一样的!!!自定义打包规则
+ 基础配置
+ HtmlWebpackPlugin 打包编译HTML的
+ 把打包后的JS/CSS自动导入到页面中
+ 对HTML进行压缩处理
+ ...
+ webpack-dev-server
项目开发的流程:本地开发「边开发、边看效果 开发环境」-> 提测「打包,部署到测试服务器」-> 重新打包,部署到服务器上「生产环境」
+ 基于Node在客户端本地启动一个Web服务,帮助开发者预览开发的作品
+ 第一步:项目打包「打包后的内容没有放在dist目录下,放在了虚拟内存中」
+ 第二步:启动web服务器「从虚拟内存中获取打包的内容,进行实时预览」
+ 第三步:热更新「当代码修改后,会实时进行打包编译,自动刷新浏览器,渲染最新的效果」 => vscode中的LiveServer插件也是做这个事情的
+ 启动的Web服务器,可以作为数据跨域请求的代理服务器,也就是可以实现Proxy跨域代理!!
+ ...
最终代码:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); module.exports = { /* 基础配置 */ mode: 'production', entry: './src/index.js', output: { filename: '[name].[hash:8].js', path: path.resolve(__dirname, './dist') }, /* 优化项 */ optimization: { // 设置压缩方式 minimizer: [ new CssMinimizerWebpackPlugin(), new TerserPlugin() ] }, /* 使用插件 */ plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html', minify: true }), new CleanWebpackPlugin(), new MiniCssExtractPlugin({ // 打包后CSS文件的名字 filename: 'main.[hash:8].css' }) ], /* DEV-SERVER */ devServer: { host: '127.0.0.1', port: 3000, open: true, hot: true, compress: true, proxy: { "/jian": { target: "https://www.jianshu.com/asimov", changeOrigin: true, ws: true, pathRewrite: { "^/jian": "" } }, "/zhi": { target: "https://news-at.zhihu.com/api/4", changeOrigin: true, ws: true, pathRewrite: { "^/zhi": "" } } } }, /* LOADER加载器:执行顺序 下->上、右->左 */ module: { rules: [{ test: /\.(css|less)$/, //基于正则匹配:哪些文件是我们需要处理的 use: [ MiniCssExtractPlugin.loader, //抽离CSS代码的 // "style-loader", //把CSS以内嵌式导入到页面 "css-loader", //处理特殊语法 "postcss-loader", //配合autoprefixer&browserslist给CSS3属性设置前缀「兼容」 "less-loader" //把less编译为css ] }, { test: /\.js$/, use: [ "babel-loader" ], // 设置编译时忽略的文件和指定编译目录「优化处理」 // include: path.resolve(__dirname, 'src'), exclude: /node_modules/ }, { test: /\.(png|jpe?g|gif)$/i, type: 'javascript/auto', //webpack5需要的 use: [{ loader: 'url-loader', options: { // 把指定大小内的图片BASE64「<=200KB则需要BASE64」 limit: 200 * 1024, esModule: false, // 编译后,没有BASE64的图片,编译输出的路径和名称 name: 'images/[name].[hash:8].[ext]' } }] }] }, /* 设置打包的最大资源大小 */ performance: { maxAssetSize: 100 * 1024 * 1024, maxEntrypointSize: 100 * 1024 * 1024 }, /* 解析器 */ resolve: { alias: { // @以后代表的就是SRC这个路径 '@': path.resolve(__dirname, './src') } } }; /* 浏览器兼容处理 设置browserslist「浏览器兼容列表」 https://github.com/browserslist/browserslist @1 考虑CSS3样式的兼容问题 postcss-loader & autoprefixer 根据browserslist,自动给CSS3加相关的前缀 @2 考虑JS的兼容性 ES6+语法兼容:把ES6+语法转换为ES5的语法 babel & babel-loader & @babel/preset-env & @babel/core & browserslist ES6+内置API的兼容:上述操作处理不了,需要导入@babel/polyfill「对常见的内置API进行了重写」 不是所有API都重写了,例如:fetch/Reflect等就没有重写!! */