《webpack实战、入门、进阶与调优》笔记

文中使用版本:webpack4.x

第一章:webpack简介

模块化思想:

    按照特定的功能将其拆分为多个代码段,每个代码段实现一个特定的目标。你可以对其进行独立的设计、开发和测试,最终通过接口来将它们组合在一起。

模块化解决了哪些问题?

  1. 依赖关系:通过导入导出语句
  2. 减少服务器请求,网络开销:资源合并
  3. 作用域污染:模块间作用域隔离

ES6模块标准应用阻力:

  1. 无法code splitting(代码分割)和tree shaking(抖掉未使用代码)
  2. npm模块大多CommonJS形式,浏览器并不支持,因此无法直接拿来用
  3. 个别浏览器及平台兼容问题

因此,在使用模块化同时兼容浏览器,需要模块打包工具
对比同类打包工具,webpack优势:

  1. 支持多种模块标准(AMD、CommonJS、ES6 module)
  2. code splitting(代码分割)
  3. 处理多类型资源(使用loader)
  4. 社区支持

Tips:
    webpack建议本地安装,避免了多个开发者之间版本不一致导致的问题
本地的webpack无法命令行直接使用“webpack”指令,工程内部只能使用npx webpack 的形式,后续简化

安装:

npm install webpack webpack-cli --save-dev

为什么要安装webpack-cli?

这里是引用

默认配置及文件:

  • 入口src/index.js
  • 出口dist/
  • 配置文件webpack.config.js

本地开发工具webpack-dev-server

Tips:
        指令npm install 和 npm install --production区别

  • npm install: 会下载全部devDependencies 和 dependencies包
  • npm install --production: 只下载dependencies包

webpack-dev-server优势:

  1. 令webpack打包:

它并不会做其他额外操作,所有webpack所需配置都要自己配置,包括所承载页面的index.html页面也需要自己加到项目

  1. 作为web Server

起一个普通的服务器,让项目可以在浏览器地址栏通过http://localhost:port访问

打包结果放在内存中,不存在于项目dist文件夹中
webpack-dev-server还有个特性live-reload(自动刷新)

  • live-reload: 自动刷新页面,全部资源会重新下载一遍
  • hot-module-replace(模块热替换):不刷新页面,只局部更新,不会再下载其他无关资源(看起来更好?)

第二章:模块打包

CommonJS:
导出:

// 🌈正确示例:
module.exports = {
	name:'calculator',
	add: function(a, b){return a + b;}
}
// 等同于
exports.name = 'calculator';
exports.add = function(a, b){return a + b;};
// ❌错误示例 
exports = {
	name:'calculator',
	add: function(a, b){return a + b;}
}
// 原因:默认会添加
// var module = {
//     exports: {}
// }
// var exports = module.exports
// 如果对exports重新定义为对象,会自动解除exports和module间的关联关系

不要将module.exports与exports混用,因为有一个会被冲掉
在导入一个模块时,整个文件都会被调用一遍,虽然收到的模块对象是module.exports或exports出来的对象,但之后的代码也会被执行一遍

module.exports= {
	name: 'calculator'
}
console.log('end')

require该模块后,console语句也会输出,但我们一般不推荐这么用,而是将它提前

导入:
使用const modA = require('moduleA')方式导入模块,如果只需要执行不需要导出对象(例如初始化操作),可以不使用const modA接收对象。

如上例,console.log(‘end’)会在第一次导入时输出,再次导入时不再输出,而是直接使用上次module.exports或exports的结果对象(值拷贝?)

require函数可接受表达式,可以利用这个特性进行动态加载模块

['modA','modB'].forEach(name => {
	require('./' + name)
})

ES6 Module:

// calculator.js
export default {
	name:'calculator',
	add: function(a, b){return a + b;}
}
// index.js
import calculator from './calculator.js'
const sum = calculator.add(2, 3)
console.log(sum) // 5
  • export import 是ES6保留字,
  • CommonJS中module不是保留字,可以被重新定义使用

ES6 Module中默认开启严格模式,不论有没有在顶部添加"use strict"字样,所以ES5转ES6的代码时要注意

导出:

  1. 命名导出

// 写法1:
export const name = ‘calculator’;

// 写法2:
const name = ‘calculator’;
export {name}
// 也可以export {name as 别名}

  1. 默认导出

// 整个js文件模块只能有一个default导出
export default {
    name: ‘calculator’
}
// 或者
export default ‘calculator’

导入:

  1. 🚩对于命名导出
export {
	name: 'calculator',
	add: function(a, b){
		return a + b	
	}
}

    导入时可以ES6解构导入(名称必须在export中存在,一一对应):

import {name, add} from './calculator.js'
// 或者使用别名(如有的变量名已经被使用) import {name, add as addSum} from './calculator.js'
add(3, 4);
// 若使用别名,则文中add方法并不存在,使用别名进行调用 如:addSum(3, 4)

    也可以整体导入(减少对当前作用域的影响)

import * as 别名 from './calculator.js'
别名.add(3, 4);
  1. 🚩对于默认导出
export default {
	name: 'calculator',
	add: function(a, b){
		return a + b	
	}
}

    导入时可以

import 别名 from './calculator.js'
别名.add(3, 4)

    可以理解为

import {default as 别名} from './calculator.js'
别名.add(3, 4)
  1. 🚩混合导入(命名导入+默认导入)
    导出:
// react.js
export Component {
	name: 'comp1'
	fn: function(){}
}
export default {
	// code
}

        导入:

 import React, {Component} from 'react'

Tips: 默认导入必须放最前,否则语法报错

复合导出

  1. 对于命名导出,进行导入再导出
// a.js
import {name, add} from './calculator.js'
export {name, add}

     可以合并为一行

// a.js
export {name, add} from './calculator.js'
  1. 对于默认导出,则不能进行复合,可以理解为没有依附的变量名
import calculator from './calculator.js'
export default calculator
CommonJS 与 ES6 Module对比
对比角度cjsesm
建立模块依      赖            动态模块依赖,可以动态require(’./’ + modName),模块依赖关系运行时确定    静态模块依赖,模块依赖关系编译时确定(一般理解为webpack打包过程中,那时bundle代码还未在服务器中运行)
模块导入值拷贝变量映射

esm静态优势:

  1. 打包时确定无用代码,减小体积
  2. 模块变量类型检查
  3. 编译器优化1

值拷贝(cjs) 与 动态映射(esm)

值拷贝:只第一次获取值并存储,方法内不可改变值,外部可随意更改值
动态映射:每次获取值,值映射可方法内部改变值,外部不可更改

循环依赖

  • cjs对于循环依赖的处理,在依赖过程中先导出module.exports这空对象
    如上文所说,module.exports是在模块顶部默认创建了module对象并添加属性exports:{},所以在代码未结束时就导出了该module.exports空对象,然后逐步继续执行。
  • esm输出的是undefined

esm利用动态映射的特性,可以对循环依赖进行控制,达到预期目的
做法:导出function实现延后执行,在function中通过开关标识进行阻断,第一次之后就进行切换标识状态,防止循环调用。

结论: ES6 Module可以更好地支持循环依赖

非模块化文件:

    对于对象绑定全局的,直接import即可

import './jquery.min.js'

    对于隐式绑定全局的,进行webpack打包后不会自动绑定到全局,因为外面加了一层function

// 在原生代码中,变量在最外层声明是会自动绑定到windows对象上的
// 但在webpack处理后,文件内容外加了一层,变成了隔离作用域,并不会自动加到全局对象上了
var calculator = {
	name: 'calculator',
	add: function(){}
}
AMD:

优点:异步不阻塞

// 定义
define(模块名, [依赖模块], function(依赖模块1,依赖模块2,...) {
	return something
})

// 调用
// 遇到require方法不等待,继续执行otherFn()
require([模块], function(模块){
	// code
})
otherFn()

缺点:

  1. 语法冗长
  2. 异步加载的方式不如同步显得清晰
  3. 易造成回调地狱
UMD:

通用模块标准,目标:使一个模块能兼容各模块环境,

注意判断顺序,一般AMD -> CommonJS -> 非模块化
而在webpack中两者都支持,使用AMD优先则不符合预期,故应该更换顺序为CommonJS -> AMD -> 非模块化

// 一般umd写法
(function (global, main) {
	if (typeof define === 'function' && define.amd) {
		define(...)	
	} else if (typeof exports === 'object') {
		module.exports = ...
	} else {
		global.add = ...
	}
}(this, function(){
	return {...}
}))

模块打包原理:
函数体中:

(function(modules) {
	// 这里有个installedModules对象为每个已经请求过的模块作缓存
	function __webpack_require__(moduleId) {
		...
	}
	return __webpack_require__(__webpack_require__.s = 0)
})({ 
// 将各个模块添到对象,需要时调用\_\_webpack\_require\_\_方法,参数为模块名0、''3qiv"、"jkzz"
	0: function(){...},
	'3qiv': function(){...},
	jkzz: function(){...},
	...
})

这是一个赋值并立即执行的匿名函数,是已经构建好的完整函数
依赖的模块方法怎么获取的,这里并未说明,后文有提到

第三章:资源输入输出

资源处理流程:
从entry入口作为根,逐级检索依赖,形成依赖树,
如果多个entry,就会有多棵树
资源入口:
单入口:
   文件字符串:chunk name默认为main
   对象形式指定[chunk name]:chunk name为指定的[chunk name]
多入口:
   对象形式指定[chunk name]:chunk name为指定的[chunk name]

context:非必填,资源入口的路径前缀,就为了其他配置写地址时候可以省略前面路径的一大堆,默认项目跟目录
entry:必填,可以是字符串、数组、对象、函数。

  •   数组中,最后一个元素为实际入口路径,常见的有
module.exports = {
	entry: ['babel-polyfill', './src/index.js']
}

等效于 在./src/index.js中 先引入了babel-polyfill模块

  •   对象中,属性也可以是字符串或 数组
  •   函数中,可以动态获取入口文件,返回一个对象,也可以返回一个Promise对象

  1. cjs: require引入时查找整个对象,且值拷贝;
    esm: import引入时以变量方式,减少引用层级 ↩︎

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值