20230418----重返学习-模块化开发-webpack打包
day-052-fifty-two-20230418-模块化开发-webpack打包
模块化开发
- 用于确保文件引入的顺序
单例设计模式
-
单例设计模式:如果有多个模块,模块之间存在依赖关系,那我们在html中导入js的时候,我们需要注意模块之间的依赖关系【模块依赖关系越复杂,就会变得很恶心】
-
例子
-
./index.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> </head> <body><h1>11111</h1><script src="./ModelA.js"></script><script src="./ModelB.js"></script><script src="./index.js"></script> </body> </html>
-
./ModelA.js
let ModelA = (() => {//求和const sum = function sum(...args) {return args.reduce((res, item) => res + item, 0);};return {sum,}; })();
-
./ModelB.js
let ModelB = (() => {//求平均值const average = function average(...args) {let sum = ModelA.sum(...args);return sum / args.length;};return {average,}; })();
-
./index.js
// console.log(`ModelA-->`, ModelA); console.log(`ModelA.sum(1,2,3,4,5,6,7)-->`, ModelA.sum(1, 2, 3, 4, 5, 6, 7)); console.log(`ModelB.average(1,2,3,4,5,6,7)-->`, ModelB.average(1, 2, 3, 4, 5, 6, 7));
-
-
优点
- 兼容性好
- 依赖关系简单
-
-
普通的script标签导入关系,只适合做简单的依赖关系,复杂的如相互循环关系不行
A B C B依赖A C依赖B和A
<script src="./ModelA.js"></script><script src="./ModelB.js"></script><script src="./index.js"></script>
-
如:
A B C D B依赖A和C C依赖A和D D依赖B和C
-
AMD(require.js)
AMD是依赖于require.js的写法,不过目前2023年差不多被淘汰了。
-
AMD思想,是在单例模式的基础上,实现了依赖的管控【管控模式:依赖前置 在具体开发模块代码之前,把所有需要的依赖先处理好,再去开发】
-
依赖于其它模块并导出当前模块
define(['A', 'B'], function (A, B) {'use strict';return {} });
-
-
grunt、glup、fis都是基于AMD思想开发的
-
例子:
-
./index.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> </head> <body><h1>AMD</h1><script src="./require.min.js"></script><script src="./index.js"></script> </body> </html>
-
./ModelA.js
//导出文件--模块A不依赖于其它模块 define(function (require, factory) {"use strict";return {sum(...args) {return args.reduce((res, item) => res + item, 0);},}; });
-
./ModelB.js
//导出文件--模块B依赖于模块A define(["ModelA"], function (ModelA) {return {average(...args) {let sum = ModelA.sum(...args);return sum / args.length;},}; });
-
./index.js
//导入文件,依赖于模块A与模块B require(["ModelA", "ModelB"], function (ModelA, ModelB) {console.log(`ModelA-->`, ModelA);console.log(`ModelA.sum(1,2,3,4,5)-->`, ModelA.sum(1, 2, 3, 4, 5));console.log(`ModelB.average(1,2,3,4,5)-->`, ModelB.average(1, 2, 3, 4, 5)); });
CMD
- CMD解决了前置依赖的问题CommonJS(后台–nodejs)/sea.js(前端)
- nodejs --> 前端的JavaScript
- 基本上支持所有的JavaScript的语法
- 命令提示符(黑框) --> node XXX.js
- node写法
-
./ModelA.js
//导出 单个值 module.exports = function sum(...args) {return args.reduce((res, item) => res + item, 0); };
-
./ModelB.js
let sum = require("./ModelA");function average(...args) {let res = sum(...args);return res / args.length; } let num = 100;//导出,导出多个内容 module.exports = {average,num, };
-
./index.js
// console.log(`111-->`, 111);let path = require("path"); //node.js自身的模块 //导入,可以省略`.js`后缀,不能省略`./`(自己创建的模块);不加`./`,默认是node.js自己的模块。 // 导入单个内容 let sum = require("./ModelA"); console.log(`sum-->`, sum);//sum--> [Function: sum] console.log(`sum(1,2,3,4,5)-->`, sum(1, 2, 3, 4, 5));//sum(1,2,3,4,5)--> 15// 导入多个内容 // let ModelB=require('./ModelB') // console.log(`ModelB-->`, ModelB);// 导入多个内容,可以使用解构赋值 let { average, num } = require("./ModelB"); console.log(`average(1,2,3,4,5)-->`, average(1, 2, 3, 4, 5));//average(1,2,3,4,5)--> 3 console.log(`num-->`, num);//num--> 100
-
要用
node.js
中的node xxx
命令来执行,这里在index.js
所在目录中使用node index.js
来调用。
-
ES6中的Module
-
在script标签中设置type=“module”,可以直接在浏览器端开启ES6Module规范,并且需要保证页面是基于HTTP/HTTPS协议预览–即本地开发要使用类似于
Live Server
的本地服务器<script type="module" src="main.js"></script>
-
ES6Module规范
-
模块中只想导出一次,直接基于
export default
导出即可-
导出:
export default sum; export default {sum:sum};
-
导入:
import sum from './A';
import A from './A'; //=> A.sum();
-
不能直接给A解构赋值
import {sum} from './A';
这样报语法错误
-
-
模块中想导出多次,导出多个方法(或者变量)给外面用 export
-
导出:
export {sum};
直接导出对象是不支持的,或者obj={}
,我们export obj
这样也不支持,像这样做需要使用export default
处理export let num = 10; export function fn() {};
-
导入:
import A from './A';
这样写也是不行的,这种方式只支持export default
import {num,fn} from './A'; import * as C from './C.js'; => C.fn();
-
-
原理:浏览器底层机制,ES6中模块导出是按照
导出一个对象
处理的- 基于
export
导出的时候,浏览器底层机制,是按照导出一个对象
处理的,每一次调用,都是设置为一个键值对。import {num,fn} from './A'
,导出语句如果遇到了花括号,后面导出的值就是导出对象
。import * as ModelA from './A'
,导出语句如果遇到了*号,后面导出的值就是导出对象
,as表示设置别名。
- 基于
export default
导出的东西,是赋值给导出对象
的default属性
的。import ModelA from './A'
,导出语句如果没遇到花括号,后面导出的值就是导出对象.default
。
- 基于
{// 基于 export 导出的内容,直接设置为键值对 num:10,fn(){},// 基于 export default 导出的东西,是赋值给对象的default属性的default:xxx}
- 所以,当我们基于import的时候,如果后面是一个花括号的形式,就意为解构赋值,就是对整个模块进行解构。而如果是一个对象时,就意为接收了
模块.default
这个属性。-
为啥 export default 导出的不能直接结构赋值?
-
因为它导出的内容,在对象的default属性中,我们需要先拿到default属性的值,才能解构处理…
import A from './A'; //-> 会默认让 A=`对象.default ` let {sum} = A;
-
-
为啥 export 导出的可以解构赋值?
- 因为它是直接把内容付给对象的键值对,导入的时候直接解构即可…
-
import * as X from ‘./A’; 此时它会把导出的对象赋值给X
-
-
- 例子:
-
./index.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> </head> <body><h1>ES6 Module</h1><script type="module" src="./index.js"></script> </body> </html>
-
./index.js
//不可以省略后缀.js // import ModelA from "./ModelA.js"; // console.log(`ModelA-->`, ModelA);//注意 `export default`导出的不能直接解构赋值,但可以用一个变量A接收之后再另起一行对变量A进行解构。 //是因为底层对`export default`导出的东西放在`{default:{}}`中的`default属性`里了。 // import {sum,num} from "./ModelA.js"; // console.log(`num-->`, num);import {default as theDefault} from "./ModelA.js" console.log(`theDefault-->`, theDefault); // import {'default':{sum,num}} from "./ModelA.js"//default是关键字,会报错 // console.log(`theDefault-->`, theDefault);// import ModelA from "./ModelA.js"; // console.log(`ModelA-->`, ModelA); // let { sum, num } = ModelA; // console.log(`num-->`, num);import { average } from "./ModelB.js"; console.log(`average-->`, average);// import { a, b, c } from "./ModelC.js"; // console.log(`a, b, c-->`, a, b, c);//*表示导入全部,as表示起个别名 import * as ModelC from "./ModelC.js";//代码运行的时候,import会自动提升到顶部,所以随意写位置都可以。 console.log(`ModelC.a, ModelC.b, ModelC.c-->`, ModelC.a, ModelC.b, ModelC.c);
-
./ModelA.js
//单个导出---一个js文件可以出现多次 // export// 多个导出--一个js文件只能出现一次 // export defaultconst sum = function sum(...args) {return args.reduce((res, item) => res + item, 0); }; let num = 100; export default {sum,num, };
-
./ModelB.js
import ModelA from "./ModelA.js";export function average(...args) {let sum = modelA.sum(...args);return sum / args.length; }
-
./ModelC.js
export const a = 100; export const b = 200; export const c = 300;// 可以声明变量,值为对象 export const obj = {n: 100,m: 200, };// export不能直接导出对象 // export { // nn:100, // mm:200 // } // 能直接导出对象的是`export default` export default {nn: 100,mm: 200, }; // `export default`可以与`export`共存,但不能出现多次,即整个js文件中只能出现一次`export default`。
-
webpack打包
-
安装webpack
-
本地安装
npm i webpack@5.49.0 webpack-cli@4.7.2
-
好处:不同的文件夹可以安装不同的版本,一般就本地安装
-
本地安装之后使用的方法
-
*方法1:
-
找到 package.json —> “scripts” 配置脚本命令
- 名称可以自定义
{"scripts": {"自定义脚本命令":"webpack"} }
-
在对应目录中执行配置的脚本命令
npm run 自定义脚本命令 // npm run serve//如果package.json中的"scripts"配置的scripts下的属性名称为serve
-
-
*方法2:
-
在对应目录中执行配置的脚本命令npx webpack (webpack5版本及以上)
npx webpack
-
-
-
默认打包的js是src目录下index.js为入口文件----》dist文件夹main.js为打包后的出口文件
-
-
全局安装
npm i webpack@5.49.0 webpack-cli@4.7.2 -g //安装 npm uni webpack@5.49.0 webpack-cli@4.7.2 -g //删除
-
缺点:只能安装某一个版本
-
全局安装之后可以在全局环境中的命令终端中使用命令 webpack
webpack //webpack执行
-
- webpack一般要高一个版本,webpack-cli一般要低一个版本。如果相同版本,一般会报兼容性错误。
-
-
使用webpack
-
本地安装之后使用的方法
-
*方法1:
-
找到 package.json —> “scripts” 配置脚本命令
- 名称可以自定义
{"scripts": {"自定义脚本命令":"webpack"} }
-
在对应目录中执行配置的脚本命令
npm run 自定义脚本命令 // npm run serve//如果package.json中的"scripts"配置的scripts下的属性名称为serve
-
-
*方法2:
-
在对应目录中执行配置的脚本命令npx webpack (webpack5版本及以上)
npx webpack
-
-
-
默认打包的js是src目录下index.js为入口文件----》dist文件夹main.js为打包后的出口文件
-
webpack 支持默认配置打包:不需要自己写配置项,它默认有自己的配置项,当我们执行 webpack 的时候,会按照默认配置项打包
- 找到当前项目目录中的 src文件夹,找到文件夹中的index.js文件,作为入口,按照入口文件夹中的代码和模块依赖,最后进行打包,打包后的文件,放在dist目录中
-
-
自定义webpack打包的规则
-
自己配置webpack的配置文件。如设置入口与出口
- 在根目录中新建
webpack.config.js
-
当我们执行webpack命令的时候,首先检测有没有这个文件。
- 有,就按照这个文件提供的规则进行打包编译;
- 没有,就按照默认的规则进行打包编译。
-
简单的配置项示例
const path = require("path"); //node.js的路径模块module.exports = {entry: "./src/index.js", //入口文件路径//出口相关配置output: {filename: "index.js", //文件名称path: path.resolve(__dirname, "dist"), //在当前项目的根目录下,创建一个dist文件夹。},mode: "development", //development 开发环境-文件不会压缩,production 生产环境-会压缩 };
- 实际意思,该文件是一个js文件,以node.js的方式来执行。通过module.exports抛出一个表示webpack配置对象的对象。
-
由于是node.js的文件,故而可以引入node.js的一些插件及组件,来根据不同情况做不同处理。
const path = require("path"); //node.js的路径模块 let outputPath = path.resolve(__dirname, "dist"); //在当前项目的根目录下,创建一个dist文件夹。 console.log(`打包完成后输出的目录:outputPath-->`, outputPath);// 这个导出的对象,就是webpack的配置; module.exports = {entry: "./src/index.js", //入口文件路径//出口相关配置output: {filename: "index.js", //文件名称path: outputPath, //输出文件的目录},mode: "production", //development开发环境-文件不会压缩,production生产环境-会压缩 };
-
- 实际意思,该文件是一个js文件,以node.js的方式来执行。通过module.exports抛出一个表示webpack配置对象的对象。
-
- 在根目录中新建
-
不同环境的配置
- 执行不同的脚本命名做不同的打包,分成开发环境和生产环境
- 方法1 做多个不同的配置执行文件,每一个配置文件对应不同的环境
-
在根目录制定做多个不同的配置执行文件,每一个配置文件对应不同的环境
-
webpack.config.development.js
const path = require("path"); let outputPath = path.resolve(__dirname, "dist"); console.log(`打包完成后输出的目录:outputPath-->`, outputPath);module.exports = {entry: "./src/index.js",output: {filename: "index.[hash].js",path: outputPath,},mode: "development", //development开发环境-文件不会压缩,production生产环境-会压缩 };
-
webpack.config.production.js
const path = require("path"); let outputPath = path.resolve(__dirname, "dist"); console.log(`打包完成后输出的目录:outputPath-->`, outputPath); module.exports = {entry: "./src/index.js",output: {filename: "index.[hash].min.js",path: outputPath,},mode: "production", //生产环境-会压缩 };
-
这个是公司常用的,一个配置对应一个环境,更简洁。逻辑更好理解。
-
-
在package.json中的"scripts"定义的不同执行命令中,使用
webpack --config
来指定webpack的配置执行文件。
-
- 方法2 :相同的配置文件,只不过根据环境变量不同,做一些不同的配置而已。
- 使用cross-env,拿到cross-env命令执行时配置的NODE_ENV属性的值,根据NODE_ENV属性的值,对同一个配置文件进行处理。
-
安装cross-env
npm i cross-env --save-dev
-
在package.json中的"scripts"定义的不同执行命令中,使用
cross-env NODE_ENV=xxxx
来指定NODE_ENV的值。{"scripts": {"开发": "cross-env NODE_ENV=development webpack","D": "cross-env NODE_ENV=development webpack","生产": "cross-env NODE_ENV=production webpack","P": "cross-env NODE_ENV=production webpack",}, }
-
在webpack.config.js中通过process.env.NODE_ENV拿到使用cross-env脚本执行时设置的NODE_ENV的值,并根据它对module.exports要返回出去的对象的值进行处理。
const path = require("path"); //node.js的路径模块 let outputPath = path.resolve(__dirname, "dist"); //在当前项目的根目录下,创建一个dist文件夹。 // console.log(`打包完成后输出的目录:outputPath-->`, outputPath);let NODE_ENV = process.env.NODE_ENV || `production`; const theMode = NODE_ENV; let outputFilename = `production.js`; console.log(`NODE_ENV-->`, NODE_ENV); console.log(`process.env.NODE_ENV-->`, process.env.NODE_ENV);if (NODE_ENV === `development`) {outputFilename = `development.js`; }// 这个导出的对象,就是webpack的配置; module.exports = {entry: "./src/index.js", //入口文件路径//出口相关配置output: {filename: outputFilename, //文件名称path: outputPath, //输出文件的目录},mode: theMode, //development开发环境-文件不会压缩,production生产环境-会压缩 };
-
命令终端窗口打开调用package.json中脚本命令
npm run D
-
- 使用cross-env,拿到cross-env命令执行时配置的NODE_ENV属性的值,根据NODE_ENV属性的值,对同一个配置文件进行处理。
- 方法1 做多个不同的配置执行文件,每一个配置文件对应不同的环境
- 执行不同的脚本命名做不同的打包,分成开发环境和生产环境
-
-
生成html文件
- 安装html插件,用于在webpack中可以生成一个html文件。
-
配置开发服务器
-
清除旧文件
-
安装clean-webpack-plugin插件
npm i clean-webpack-plugin –save-dev
-
在webpack.config.js中插入该插件。
const pluginsList = [];//"clean-webpack-plugin"用于清除之前打包后文件,再打包新的文件。 // 1. 导入插件 let { CleanWebpackPlugin } = require("clean-webpack-plugin"); const theCleanWebpackPlugin = new CleanWebpackPlugin(); pluginsList.push(theCleanWebpackPlugin);module.exports = {plugins: pluginsList, //2. 使用插件,是一个数组。 }
-
在package.json中scripts脚本中配置webpack命令
{"scripts": {"build": "webpack --config webpack.config.production.js",//这个是打包命令,在出口文件目录产生文件,配置好了clean-webpack-plugin后会清除上次的出口文件"D": "cross-env NODE_ENV=development webpack",//这个是打包命令,在出口文件目录产生文件,配置好了clean-webpack-plugin后会清除上次的出口文件"fang": "webpack",//这个是打包命令,在出口文件目录产生文件,配置好了clean-webpack-plugin后会清除上次的出口文件"fang": "webpack server"//这个是调用本地服务器,不会在出口文件目录产生文件,不会清除上次的出口文件}, }
-
命令终端窗口打开调用package.json中脚本命令
npm run build
-