大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。
网上很多关于模块化的资料,但是每次提起还有遇到package.json
中的打包配置还是理不清,那些之前搞不懂的知识点迟早还是要还的,每次学习也是一个查漏补缺的过程。加油!!!
理完这些大概理解,为什么会有webpack
,rollup
,vite
这些打包工具了,模块是大佬们为了解决前期代码分割问题提出的解决方案。前期是通过拆分一个js到多个文件实现的模块化,但是文件多了之后无法保证命名问题,大佬们就剔除了模块化的方案。
模块化的好处:
- 解决命名冲突问题
- 解决变量查找作用域的层级,减少性能消耗
- 代码逻辑更清晰,将功能和模块化绑定更易维护
- 利用模块化还能更方便的对现有库进行二次封装不会影响原来库
打包源代码 index.js
javascript
import { add } from './func.mjs'
console.log('hello world')
function sayHi() {
let name = 'jane'
console.log(name + 'hello world', add(1, 2))
}
sayHi()
window.addEventListener('error', function (env) {
console.log(env)
})
func.mjs
css
export function add(a, b) {
return a + b
}
使用rollup
打包结果
amd 异步模块定义
异步模块定义,采用异步方式加载模块,类似于回调函数 。使用define
定义模块,不支持原生Js,需要借助库来使用。
使用参考这边的例子,为什么需要在script
标签中加一个data-main
属性,只有在使用require.js
的时候才这么用,表示入口文件是哪个。
适合在浏览器环境中异步加载模块,可以并行加载多个模块
打包工具配置打包格式可以将所有依赖项打包成一个文件,生成一个入口文件,因此查看打包好的配置为umd
格式的文件时看不到后面几个参数,只有第一个参数,就是一个函数。
第一个参数:模块名字
第二个参数:模块依赖项数组
第三个参数:函数的参数与前面依赖项一一对应,每一项分别为依赖项模块的导出成员。
javascript
define((function () { 'use strict';
function add(a, b) {
return a + b
}
console.log('hello world');
function sayHi() {
let name = 'jane';
console.log(name + 'hello world', add(1, 2));
}
sayHi();
window.addEventListener('error', function (env) {
console.log(env);
});
}));
umd通用模块化规范
amd 和commonjs的结合,判断是否支持node.js模块,然后使用Node.js模块模式,再判断是否支持AMD,使用AMD加载模块
umd源码范式推演,可以参考这篇文章
通过传参方式导出一个模块,Umd功能根据使用要求生产模块,它的职责定位是模块工厂,我们定义一个factory方法,每当执行时,返回一个模块
javascript
(function(factory){
//通过形参访问工厂方法,如果不着地那个挂载对象,就默认挂载到全局对象
window.attr = factory()
})(function(){})
指定挂载对象
javascript
(function(root,factory){
root.attr = factory();
}(self !== undefined ? self : this, function(){
}));
适配amd
javascript
(function(factory){
root.attr = factory();
if(typeof define === 'function' && define.amd){
define(factory)
}
}(self !== undefined ? self : this, function(){
}));
适配commonjs
javascript
(function(factory){
root.attr = factory();
if(typeof exports === 'object' && typeof define !== 'function'){
module.exports = factory()
}
}(self !== undefined ? self : this, function(){
}));
cjs commonjs
cjs
语法基本和js
一致,只不过是运行在node
环境的代码,node
也支持js
语法,官网上介绍说Node.js是一个开源、跨平台的 JavaScript 运行时环境 ,本身更是支持文件读取、http请求服务等操作,比起js
只能操作浏览器对象,node
可一操作一些操作系统层面的东西。
关于npm
配置在node和浏览器环境配区别问题,在es modules
章节讲
javascript
'use strict';
function add(a, b) {
return a + b
}
console.log('hello world');
function sayHi() {
let name = 'jane';
console.log(name + 'hello world', add(1, 2));
}
sayHi();
window.addEventListener('error', function (env) {
console.log(env);
});
es
可以用编译阶段就确定模块的依赖关系,commonjs和amd模块,只能在运行时确定。
现在浏览器已经支持用script标签引入模块或脚本,需要在script
中添加type="module"
,这样就可以解析import/export
语法
打包类型配置为es
也就是esModule
即js
,就不做过多介绍了。
以下属性配置是一些非标准的属性,在npm
官网package.json
配置项中找不到
ruby
{
"type":"",//声明npm包遵循的模块化规范,默认:commonjs,可设置值:commonjs/module
"module":"",//如果npm包导出的是ESM规范的包,可以使用module来定义入口文件
}
如果在npm package.json
中设置type:'module'
例:修改type=module
后表示使用es module
规范
如果在npm package.json
中设置module:'index.mjs'
表示定义npm
包的ESM规范的入口文件
main
字段,定义入口文件,客户端和服务端都可以使用
browser
字段,官网上描述,如果是在客户端使用,则为了更加语义化应该使用browser
字段代替main
字段
如果package.json
中同时指定了main、module、browser
这三个字段,则有优先级
package.json中Browser modules main字段优先级对比
大致理解就是会先判断是否有module
,因为module
设置了就表示使用es module
规范
如果有module
就判断browser
main
的配置其实兜底逻辑
涉及到的文件加载顺序问题
文件加载优先级mjs > js
mjs
设置为.mjs
结尾的文件可以在node环境下原生执行ESM规范的脚本文件
npm包怎么根据在不同环境下加载npm包不同的入口文件?
- 使用
process
对象检测
arduino
if (typeof process !== 'undefined') {
console.log('运行环境是Node.js')
} else {
console.log('运行环境不是Node.js')
}
- 使用
window
对象检测
javascript
if (typeof window !== 'undefined') {
console.log('运行环境是浏览器')
} else {
console.log('运行环境不是浏览器')
}
打包结果
javascript
function add(a, b) {
return a + b
}
console.log('hello world');
function sayHi() {
let name = 'jane';
console.log(name + 'hello world', add(1, 2));
}
sayHi();
window.addEventListener('error', function (env) {
console.log(env);
});
iife立即执行函数
立即执行函数,可以利用立即执行函数创建闭包解决变量作用域问题
还可以利用立即执行函数避免全局变量命名冲突问题
因为创建了一个独立的作用域,因此变量查找作用域时可以减少对作用域的查找
javascript
(function () {
'use strict';
function add(a, b) {
return a + b
}
console.log('hello world');
function sayHi() {
let name = 'jane';
console.log(name + 'hello world', add(1, 2));
}
sayHi();
window.addEventListener('error', function (env) {
console.log(env);
});
})();