1.express.js VS koa.js
- 中间件模型不同:express 的中间件模型为线型,而 koa 的为U型(洋葱模型)
- 对异步的处理不同:express 通过回调函数处理异步,而 koa 通过generator 和 async/await 使用同步的写法来处理异步,后者更易维护,但彼时 Node.js 对 async 的兼容性和优化并不够好,所以没有流行起来
- 功能不同:express 包含路由、渲染等特性,而 koa 只有 http 模块
PS:express 和 koa 的作者都是 TJ 大神
2.nodejs 多进程
- 进程,是操作系统进行资源调度和分配的基本单位,每个进程都拥有自己独立的内存区域
- 线程,是操作系统进行运算调度的最小单位,一个进程可以包含多个线程,多线程之间可共用进程的内存数据
- 一个进程无法直接访问另一个进程的内存数据,除非通过合法的进程通讯
- 执行一个 nodejs 文件,即开启了一个进程,可以通过
process.pid
查看进程 id - 如操作系统是一个工厂,进程就是一个车间,线程就是一个一个的工人
- JS 是单线程的,即执行 JS 时启动一个进程(如 JS 引擎,nodejs 等),然后其中再开启一个线程来执行
- 虽然单线程,JS 是基于事件驱动的,它不会阻塞执行,适合高并发的场景
为何需要多进程
- 现代服务器都是多核 CPU ,适合同时处理多进程。即,一个进程无法充分利用 CPU 性能,进程数要等于 CPU 核数
- 服务器一般内存比较大,但操作系统对于一个进程的内存分配是有上限的(2G),所以多进程才能充分利用服务器内存
nodejs 开启多进程
-
child_process.fork
可开启子进程执行单独的计算fork('xxx.js')
开启一个子进程- 使用
send
发送信息,使用on
接收信息
-
cluster.fork
可针对当前代码,开启多个进程来执行
扩展:使用 PM2
- nodejs 服务开启多进程、进程守护,可使用 pm2
- 全局安装 pm2
yarn global add pm2
- 增加 pm2 配置文件
- 修改 package.json scripts
3.loader 和 plugin
其实这些官网介绍的很详细:Loaders | webpack Plugins | webpack
常见loader
babel-loader
将es6 语法转换为es5语法ts-loader
把 TS 变成 JS,并提示类型错误markdown-loader
把 markdown 变成 htmlhtml-loader
把 html 变成 JS 字符串url-loader
处理其他资源,也可以处理图片资源sass-loader
把 SASS / SCSS 变成 CSScss-loader
把 CSS 变成 JS 字符串style-loader
把 JS 字符串变成 style 标签postcss-loader
把 CSS 变成更优化兼容的 CSSvue-loader
把单文件组件(SFC)变成 JS 模块thread-loader
用于多进程打包
常见plugin
html-webpack-plugin
根据指定模板,自动创建html文件,且自动引入外部资源clean-webpack-plugin
用于清理之前打包的残余文件mini-css-extract-plugin
用于提取项目中的css为一个单独的文件SplitChunksPlugin
用于代码分包(Code Split)DllPlugin
+DllReferencePlugin
用于避免大依赖被频繁重新打包,大幅降低打包时间 webpack使用-详解DllPlugin - SegmentFault 思否eslint-webpack-plugin
用于检查代码中的错误DefinePlugin
用于在 webpack config 里添加全局变量copy-webpack-plugin
用于拷贝静态文件到 dist@babel/polyfill
用于处理JS兼容性问题,例如IE浏览器不支持Promiseoptimize-css-assets-webpack-plugin
用于压缩css
二者的区别
webpack本身只能打包js和json
- loader 是文件加载器,是对指定的资源进行处理,将资源文件从输入到输出之间的一个转换,将资源文件转化为 js 模块
- plugin 是 webpack 插件,拥有更宽的能力范围,
Webpack
要求插件必须是一个函数或者是一个包含apply
方法的对象,通过在生命周期的钩子中挂载函数实现扩展。插件机制目的是为了增强webpack
自动化方面的能力
开发Loader
要专注实现资源模块的加载,从而去实现整体项目的打包
loader
文件markdown-loader.js
:
const marked = require('marked')
module.exports = source => {
// console.log(source)
// return 'console.log("hello")'
const html = marked(source)
console.log(html)
// 两种导出方式:
// return `module.exports=${JSON.stringify(html)}`
return `export default ${JSON.stringify(html)}`
}
在webpack.config.js
中如何使用:
module: {
rules: [
{
test: /.md$/,
use: ['html-loader', './markdown-loader.js']
}
]
}
开发 Plugin 是为了解决除了资源加载以外的其他的一些自动化工作。
开发的插件可以是一个函数或者是一个包含 apply 方法的对象:
class MyPlugin {
apply (compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation 可以理解为此次打包的上下文
for (const name in compilation.assets) {
// console.log(name) // 文件名
console.log(compilation.assets[name].source())
if(name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\//g, '')
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
如何使用自定义插件:
plugins: [
new MyPlugin()
]
4.webpack 如何解决开发时的跨域问题
- 在开发时,我们的页面在
localhost:8080
,JS 直接访问后端接口(如https://yunmu.com
或http://localhost:3000
)会报跨域错误。 - 为了解决这个问题,可以在 webpack.config.js 中添加如下配置
module.exports = {
//...
devServer: {
// 请求 /api/users 就会自动被代理到 http://xiedaimala.com/api/users
proxy: {
'/api': {
target: 'http://yunmu.com',
// 使请求中的 Origin 从 8080 修改为 xiedaimala.com
changeOrigin: true,
// 不配置证书且忽略 HTTPS 报错
secure: false
},
},
},
};
5.如何实现 tree-shaking
tree-shaking 就是让没有用到的 JS 代码不打包,以减小包的体积
同时满足两个条件 webpack 会自动开启 tree-shaking
- 使用 ES Module 语法,CommonJS 语法无法 tree-shaking
- 开启 production 环境
可以配置 sideEffects 防止默写文件被 tree-shaking 掉
- 比如我 import 了 x.js,而 x.js 只是添加了 window.x 属性,那么 x.js 就要放到 sideEffects 里
- 比如所有被 import 的 CSS 都要放在 sideEffects 里
6.如何提高 webpack 的构建速度
构建性能 | webpack 中文文档 (docschina.org)
- 使用 DllPlugin 将不常变化的代码提前打包,并复用,如 vue、react
- 使用 thread-loader 或 HappyPack(过时)进行多线程打包
- ParallelUglifyPlugin 多进程压缩JS
- 处于开发环境时,在 webpack config 中将 cache 设为 true
- 处于生产环境时,关闭不必要的环节,比如可以关闭 source map
- 优化 babel-loader 开启缓存,include 和 exclude 确定范围
- IgnorePlugin 避免引入无用模块
- noParse 避免重复打包
7.Vite VS Webpack
- Vite 开发环境使用
<script type=module>
引入文件,当请求某个文件的时候才会处理该文件,生产环境使用 rollup + esbuild 来打包 JS 代码 - webpack 开发环境 webpack-dev-server 会使用 babel-loader 将代码递归的打包进内存里然后再进行请求,生产环境使用 babel 来打包 JS 代码
PS:Vite热更常常失败,有些功能需要自己写 rollup 插件,并且不支持现代浏览器
8.webpack 配置多页应用
对应的 webpack config:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'admin.html',
chunks: ['admin']
})
],
};
这样打包会产生一个重复打包的问题
假如 app.js 和 admin.js 都引入了 vue.js,那么 vue.js 的代码既会打包进 app.js,也会打包进 admin.js
我们需要使用 optimization.splitChunks
将共同依赖单独打包成 common.js(HtmlWebpackPlugin 会自动引入 common.js)
支持无限多的页面:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const fs = require('fs')
const path = require('path')
const filenames = fs.readdirSync('./src/pages')
.filter(file => file.endsWith('.js'))
.map(file => path.basename(file, '.js'))
const entries = filenames.reduce((result, name) => (
{ ...result, [name]: `./src/pages/${name}.js` }
), {})
const plugins = filenames.map((name) =>
new HtmlWebpackPlugin({
filename: name + '.html',
chunks: [name]
})
)
module.exports = {
entry: {
...entries
},
plugins: [
...plugins
],
};
9.swc、esbuild 是什么?
swc
实现语言:Rust
功能:编译 JS/TS、打包 JS/TS
优势:比 babel 快很多很多很多(20倍以上)
能否集成进 webpack:能
使用者:Next.js、Parcel、Deno、Vercel、ByteDance、Tencent、Shopify……
做不到:
-
对 TS 代码进行类型检查(用 tsc 可以)
-
打包 CSS、SVG
esbuild
实现语言:Go
功能:编译 JS/TS、打包 JS/TS
优势:比 babel 快很多很多很多很多很多很多(10~100倍)
能否集成进 webpack:能
使用者:vite、vuepress、snowpack、umijs、blitz.js 等
做不到:
-
对 TS 代码进行类型检查
-
打包 CSS、SVG
10.webpack优化产出代码
- 小图片base64编码
- 提取公共代码
- bundle 加hash
- 使用CDN加速
- 懒加载
- IgnorePlugin
- 使用production
- Scope Hosting
11.ES6 Module和 Commonjs区别
- ES6 Module 静态引入,编译时引入
- Commonjs 动态引入,执行时引入
- 只有 ES6 Module 才能静态分析,实现 Tree-Shaking
12.使用 Webpack 实现 Vue 项目打包任务
webpack.common.js
const path = require('path');
const webpack = require('webpack');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: path.join(__dirname, 'src/main.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
},
resolve: {
extensions: ['.js', '.json', '.css', '.vue'],
},
devServer: {
contentBase: path.join(__dirname, 'public'),
port: 5000,
hot: true,
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader'],
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader'],
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024, // 单位是字节 10KB
esModule: false,
},
},
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: 'Jal-Vue-Webpack',
template: './public/index.html',
filename: 'index.html',
}),
new webpack.DefinePlugin({
BASE_URL: JSON.stringify('/'),
}),
],
};
Webpack.dev.js
const common = require('./webpack.common');
const merge = require('webpack-merge');
module.exports = merge(common, {
mode: 'development',
optimization: {
usedExports: true,
minimize: true,
},
devtool: 'eval-cheap-module-source-map',
});
Webpack.prod.js
const path = require('path');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production',
output: {
filename: '[name]-[contenthash:8].bundle.js',
path: path.join(__dirname, 'dist'),
},
devtool: 'none',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: ['public'],
}),
],
});
13.webpack的打包构建流程
- 连接: webpack从入口JS开始,递归查找出所有相关的模块, 并【连接】起来形成一个图(网)的结构
- 编译: 将JS模块中的模块化语法【编译】为浏览器可以直接运行的模块语法(当然其它类型资源也会处理)
- 合并: 将图中所有编译过的模块【合并】成一个或少量的几个文件, 浏览器真正运行是打包后的文件
14.live-reload(自动刷新)与HMR(热模替换)
- 相同点: 代码修改后都会自动重新编译打包
- 不同点:
- live-reload: 刷新整体页面,从而查看到最新代码的效果,页面状态全部都是新的
- HMR: 没有刷新整个页面,只是加载了修改模块的打包文件并运行,从而更新页面的局部界面,整个界面的其它部分的状态还在