CommonJS
var b = require('./b')
CommonJS在浏览器内并不适用. 因为require()的返回是同步的, 意味着有多个依赖的话需要一个一个依次下载, 堵塞了js脚本的执行.
深入浅出node.js
CommonJS的模块规范
1.模块引用
require()方法
用const
2.模块定义
对应引入的功能,上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。在模块中,还存在一个module对象,它代表模块自身,而exports是module的属性。在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出的方式。
(function)
3.模块标识
Node的模块实现
1.路径分析
2.文件定位
(1)核心模块,如http、fs、path等。
(2).或者..开始的相对路径文件模块。
(3)以/开始的绝对路径文件模块。
(4)非路径形式的文件模块,通常是npm下载的模块,如connect模块。从node_modules目录里找
-g全局模式安装
全局模式并不是将一个模块包安装为一个全局包的意思,它并不意味着可以从任何地方通过require()来引用它。
-g是将一个包安装为全局可用的可执行命令。它根据包描述文件中(package.json)的bin字段配置,将实际脚本链接到与Node可执行文件相同的路径下:
“bin”: {
“express”: "./bin/express"
}
事实上,通过全局模式安装的所有模块包都被安装进了一个统一的目录下,如果Node可执行文件的位置是/user/local/bin/node,那么模块目录就是/user/local/lib/node_modules。最后,通过软链接的方式将bin字段配置的可执行文件链接到Node的可执行目录下。
例如环境变量的path中有E:\nodejs\,打开命令行工具任意文件下都可以使用node命令node -v,全局安装express后,将express模块的bin文件夹下的可执行文件的快捷方式会放进E:\nodejs\,以后任意文件下也可以使用express命令行。
3.编译执行
Node对获取的JavaScript文件内容进行了头尾包装。
(function(exports,require,module,__filename,__dirname){...}
前后端JavaScript分别搁置在HTTP的两端,它们扮演的角色并不同。浏览器端的JavaScript需要经历从同一个服务器端分发到多个客户端执行,而服务器端JavaScript则是相同的代码需要多次执行。前者的瓶颈在于带宽,后者的瓶颈则在于CPU和内存等资源。前者需要通过网络加载代码,后者从磁盘中加载,两者的加载速度不在一个数量级上。
纵观Node的模块引入过程,几乎全都是同步的。尽管与Node强调异步的行为有些相反,但它是合理的。但是如果前端模块也采用同步的方式引入,那将会在用户体验上造成很大的问题。UI在初始化过程中需要花费很多时间来等待脚本加载完成。
鉴于网络的原因,CommonJS为后端JavaScript制定的规范并不完全适合前端的应用场景。经过一段争执之后,AMD规范最终在前端应用场景中胜出。
Asynchronous Module Definition (AMD)
require(['./b', './c'], function(b, c) {
var n = b.square(2)
console.log(c) // 3.14159
})
define(['./d'], function(d) {
return d.PI
})
webpack

├── dist 打包输出目录, 只需部署这个目录到生产环境
├── package.json 项目配置信息
├── node_modules npm安装的依赖包都在这里面
├── src 我们的源代码
│ ├── components 可以复用的模块放在这里面
│ ├── index.html 入口html
│ ├── index.js 入口js
│ ├── shared 公共函数库
│ └── views 页面放这里
└── webpack.config.js webpack配置文件
npm init
帮助生成package.json文件
npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev
npm install可以一条命令同时安装多个包, 包之间用空格分隔. 包会被安装进node_modules目录中.
--save-dev会把安装的包和版本号记录到package.json中的devDependencies对象中, 还有一个--save, 会记录到dependencies对象中。
它们的区别, 我们可以先简单的理解为打包工具和测试工具用到的包使用--save-dev存到devDependencies, 比如eslint, webpack.
浏览器中执行的js用到的包存到dependencies, 比如jQuery等.
安装完webpack后运行
npx webpack --mode development //记得后面运行环境,npx:一个npm包执行器
提示你没有安装webpack-cli这是因为 webpack 在 webpack 4 里将命令行相关的都迁移至 webpack-cli 包。输入yes安装。
自动刷新浏览器的问题,webpack 提供 webpack-dev-server 来解决
npm install -D webpack-dev-server//-D就是--save-dev
给出项目运行地址和端口
Project is running at http://localhost:8080/
babel
npm install babel-core babel-preset-env babel-loader --save-dev
webpack.config.js
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const history = require('connect-history-api-fallback')
const convert = require('koa-connect')
// 使用WEBPACK_SERVE环境变量检测当前是否是在webpack-server启动的开发环境中
const dev = Boolean(process.env.WEBPACK_SERVE)
module.exports = {
/*
webpack执行模式
development: 开发环境, 它会在配置文件中插入调试相关的选项, 比如moduleId使用文件路径方便调试
production: 生产环境, webpack会将代码做压缩等优化
*/
mode: dev ? 'development' : 'production',
/*
配置source map
开发模式下使用cheap-module-eval-source-map, 生成的source map能和源码每行对应, 方便打断点调试
生产模式下使用hidden-source-map, 生成独立的source map文件, 并且不在js文件中插入source map路径, 用于在error report工具中查看 (比如Sentry)
*/
devtool: dev ? 'cheap-module-eval-source-map' : 'hidden-source-map',
// 配置页面入口js文件
entry: './src/index.js',
// 配置打包输出相关
output: {
// 打包输出目录
path: resolve(__dirname, 'dist'),
// 入口js的打包输出文件名
filename: 'index.js'
},
module: {
/*
配置各种类型文件的加载器, 称之为loader
webpack当遇到import ... 时, 会调用这里配置的loader对引用的文件进行编译
*/
rules: [
{
/*
使用babel编译ES6/ES7/ES8为ES5代码
使用正则表达式匹配后缀名为.js的文件
*/
test: /\.js$/,
// 排除node_modules目录下的文件, npm安装的包不需要编译
exclude: /node_modules/,
/*
use指定该文件的loader, 值可以是字符串或者数组.
这里先使用eslint-loader处理, 返回的结果交给babel-loader处理. loader的处理顺序是从最后一个到第一个.
eslint-loader用来检查代码, 如果有错误, 编译的时候会报错.
babel-loader用来编译js文件.
*/
use: ['babel-loader', 'eslint-loader']
},
{
// 匹配.html文件
test: /\.html$/,
/*
使用html-loader, 将html内容存为js字符串, 比如当遇到
import htmlString from './template.html';
template.html的文件内容会被转成一个js字符串, 合并到js文件里.
*/
use: 'html-loader'
},
{
// 匹配.css文件
test: /\.css$/,
/*
先使用css-loader处理, 返回的结果交给style-loader处理.
css-loader将css内容存为js字符串, 并且会把background, @font-face等引用的图片,
字体文件交给指定的loader打包, 类似上面的html-loader, 用什么loader同样在loaders对象中定义, 等会下面就会看到.
*/
use: ['style-loader', 'css-loader']
},
{
/*
匹配各种格式的图片和字体文件
上面html-loader会把html中<img>标签的图片解析出来, 文件名匹配到这里的test的正则表达式,
css-loader引用的图片和字体同样会匹配到这里的test条件
*/
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
/*
使用url-loader, 它接受一个limit参数, 单位为字节(byte)
当文件体积小于limit时, url-loader把文件转为Data URI的格式内联到引用的地方
当文件大于limit时, url-loader会调用file-loader, 把文件储存到输出目录, 并把引用的文件路径改写成输出后的路径
比如 views/foo/index.html中
<img src="smallpic.png">
会被编译成
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA...">
而
<img src="largepic.png">
会被编译成
<img src="/f78661bef717cf2cc2c2e5158f196384.png">
*/
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
]
},
/*
配置webpack插件
plugin和loader的区别是, loader是在import时根据不同的文件名, 匹配不同的loader对这个文件做处理,
而plugin, 关注的不是文件的格式, 而是在编译的各个阶段, 会触发不同的事件, 让你可以干预每个编译阶段.
*/
plugins: [
/*
html-webpack-plugin用来打包入口html文件
entry配置的入口是js文件, webpack以js文件为入口, 遇到import, 用配置的loader加载引入文件
但作为浏览器打开的入口html, 是引用入口js的文件, 它在整个编译过程的外面,
所以, 我们需要html-webpack-plugin来打包作为入口的html文件
*/
new HtmlWebpackPlugin({
/*
template参数指定入口html文件路径, 插件会把这个文件交给webpack去编译,
webpack按照正常流程, 找到loaders中test条件匹配的loader来编译, 那么这里html-loader就是匹配的loader
html-loader编译后产生的字符串, 会由html-webpack-plugin储存为html文件到输出目录, 默认文件名为index.html
可以通过filename参数指定输出的文件名
html-webpack-plugin也可以不指定template参数, 它会使用默认的html模板.
*/
template: './src/index.html',
/*
因为和webpack 4的兼容性问题, chunksSortMode参数需要设置为none
https://github.com/jantimon/html-webpack-plugin/issues/870
*/
chunksSortMode: 'none'
})
]
}
/*
配置开发时用的服务器, 让你可以用 http://127.0.0.1:8080/ 这样的url打开页面来调试
并且带有热更新的功能, 打代码时保存一下文件, 浏览器会自动刷新. 比nginx方便很多
如果是修改css, 甚至不需要刷新页面, 直接生效. 这让像弹框这种需要点击交互后才会出来的东西调试起来方便很多.
因为webpack-cli无法正确识别serve选项, 使用webpack-cli执行打包时会报错.
因此我们在这里判断一下, 仅当使用webpack-serve时插入serve选项.
issue: https://github.com/webpack-contrib/webpack-serve/issues/19
*/
if (dev) {
module.exports.serve = {
// 配置监听端口, 默认值8080
port: 8080,
// add: 用来给服务器的koa实例注入middleware增加功能
add: app => {
/*
配置SPA入口
SPA的入口是一个统一的html文件, 比如
http://localhost:8080/foo
我们要返回给它
http://localhost:8080/index.html
这个文件
*/
app.use(convert(history()))
}
}
}
output是输出配置
path表示输出的硬盘路径,不过热替换中编译好的代码放在内存中,此时此项无用。
filename表示输出的文件名称。
publicPath则表示公共路径,即所有资源的URL前缀。比如,如果你的HTML页面中有一个
<script src="/static/bundle.js"></script>,那么应该将publicPath设为/static/。在热替换中,这个路径是虚拟的(即无法在硬盘中找到这个路径),打包编译后的脚本被保存到了内存中。
webpackDevMiddleware中间件
Webpack本身只有打包功能,但是把这个中间件连入Express服务器后,就可以将Webpack的打包功能与Express服务器的资源服务功能合二为一**(打包后再提供)**。Express服务器通过该中间件完成打包工作,并将打包好的资源读到内存中,接下来我们就可以通过Express服务器的端口访问这些资源了。不仅如此,该中间件可以监视代码变动,一旦代码变动,就会停止提供旧版的资源服务,等到编译完成后继续提供新的资源服务。
webpackHotMiddleware中间件
webpackDevMiddleware中间件已经完成了大部分自动化工作,但是它无法实现热替换,也就是无法在更新代码后保留程序状态。为了实现热替换,还应该在webpackDevMiddleware中间件的基础上加上webpackHotMiddleware中间件。
DllPlugin 和 DllReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。
DllPlugin生成一个manifest.json文件(包含了从 require 和 import 的request到模块 id 的映射)和dll.js文件(第三方库都打包到这个js文件中)
const path = require('path')
const webpack = require('webpack')
const config = {
mode: 'production',
entry: {
vendors: [
'react',
'react-dom',
'react-router-dom',
'antd'
],
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '..', 'static', 'scripts'),
library: '[name]_lib',
},
plugins: [
new webpack.DllPlugin({
// context不能没有
context: __dirname,
name: '[name]_lib',
path: path.resolve(__dirname, '[name].manifest.json'),
})
],
}
module.exports = config
DllReferencePlugin通过引用 dll 的 manifest 文件来把依赖的名称映射到模块的 id 上
CommonJS
var b = require('./b')CommonJS在浏览器内并不适用. 因为require()的返回是同步的, 意味着有多个依赖的话需要一个一个依次下载, 堵塞了js脚本的执行.
深入浅出node.js
CommonJS的模块规范
1.模块引用
require()方法
用const
2.模块定义
对应引入的功能,上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。在模块中,还存在一个module对象,它代表模块自身,而exports是module的属性。在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出的方式。
(function)
3.模块标识
Node的模块实现
1.路径分析
2.文件定位
(1)核心模块,如http、fs、path等。
(2).或者..开始的相对路径文件模块。
(3)以/开始的绝对路径文件模块。
(4)非路径形式的文件模块,通常是npm下载的模块,如connect模块。从node_modules目录里找
-g全局模式安装
全局模式并不是将一个模块包安装为一个全局包的意思,它并不意味着可以从任何地方通过require()来引用它。
-g是将一个包安装为全局可用的可执行命令。它根据包描述文件中(package.json)的bin字段配置,将实际脚本链接到与Node可执行文件相同的路径下:
“bin”: {
“express”: "./bin/express"
}
事实上,通过全局模式安装的所有模块包都被安装进了一个统一的目录下,如果Node可执行文件的位置是/user/local/bin/node,那么模块目录就是/user/local/lib/node_modules。最后,通过软链接的方式将bin字段配置的可执行文件链接到Node的可执行目录下。
例如环境变量的path中有E:\nodejs\,打开命令行工具任意文件下都可以使用node命令node -v,全局安装express后,将express模块的bin文件夹下的可执行文件的快捷方式会放进E:\nodejs\,以后任意文件下也可以使用express命令行。
3.编译执行
Node对获取的JavaScript文件内容进行了头尾包装。
(function(exports,require,module,__filename,__dirname){...}前后端JavaScript分别搁置在HTTP的两端,它们扮演的角色并不同。浏览器端的JavaScript需要经历从同一个服务器端分发到多个客户端执行,而服务器端JavaScript则是相同的代码需要多次执行。前者的瓶颈在于带宽,后者的瓶颈则在于CPU和内存等资源。前者需要通过网络加载代码,后者从磁盘中加载,两者的加载速度不在一个数量级上。
纵观Node的模块引入过程,几乎全都是同步的。尽管与Node强调异步的行为有些相反,但它是合理的。但是如果前端模块也采用同步的方式引入,那将会在用户体验上造成很大的问题。UI在初始化过程中需要花费很多时间来等待脚本加载完成。
鉴于网络的原因,CommonJS为后端JavaScript制定的规范并不完全适合前端的应用场景。经过一段争执之后,AMD规范最终在前端应用场景中胜出。
Asynchronous Module Definition (AMD)
webpack
npm init帮助生成package.json文件
npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-devnpm install可以一条命令同时安装多个包, 包之间用空格分隔. 包会被安装进node_modules目录中.
--save-dev会把安装的包和版本号记录到package.json中的devDependencies对象中, 还有一个--save, 会记录到dependencies对象中。
它们的区别, 我们可以先简单的理解为打包工具和测试工具用到的包使用--save-dev存到devDependencies, 比如eslint, webpack.
浏览器中执行的js用到的包存到dependencies, 比如jQuery等.
安装完webpack后运行
npx webpack --mode development //记得后面运行环境,npx:一个npm包执行器提示你没有安装webpack-cli这是因为 webpack 在 webpack 4 里将命令行相关的都迁移至 webpack-cli 包。输入yes安装。
自动刷新浏览器的问题,webpack 提供 webpack-dev-server 来解决
npm install -D webpack-dev-server//-D就是--save-dev给出项目运行地址和端口
Project is running at http://localhost:8080/babel
npm install babel-core babel-preset-env babel-loader --save-devwebpack.config.js
output是输出配置
path表示输出的硬盘路径,不过热替换中编译好的代码放在内存中,此时此项无用。
filename表示输出的文件名称。
publicPath则表示公共路径,即所有资源的URL前缀。比如,如果你的HTML页面中有一个
<script src="/static/bundle.js"></script>,那么应该将publicPath设为/static/。在热替换中,这个路径是虚拟的(即无法在硬盘中找到这个路径),打包编译后的脚本被保存到了内存中。webpackDevMiddleware中间件
Webpack本身只有打包功能,但是把这个中间件连入Express服务器后,就可以将Webpack的打包功能与Express服务器的资源服务功能合二为一**(打包后再提供)**。Express服务器通过该中间件完成打包工作,并将打包好的资源读到内存中,接下来我们就可以通过Express服务器的端口访问这些资源了。不仅如此,该中间件可以监视代码变动,一旦代码变动,就会停止提供旧版的资源服务,等到编译完成后继续提供新的资源服务。
webpackHotMiddleware中间件
webpackDevMiddleware中间件已经完成了大部分自动化工作,但是它无法实现热替换,也就是无法在更新代码后保留程序状态。为了实现热替换,还应该在webpackDevMiddleware中间件的基础上加上webpackHotMiddleware中间件。
DllPlugin 和 DllReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。
DllPlugin生成一个manifest.json文件(包含了从 require 和 import 的request到模块 id 的映射)和dll.js文件(第三方库都打包到这个js文件中)
DllReferencePlugin通过引用 dll 的 manifest 文件来把依赖的名称映射到模块的 id 上