js 痛点
- 文件依赖度高;
- 命名冲突/变量污染;
- 可读性差;
模块化
- 把复杂问题分解成相对独立的模块,这样的设计可以降低程序复杂性,提高代码的重用,也有利于团队协作开发与后期的维护和扩展;
模块化规范
node:cjs-commonjs
- 有一个全局对象:module;
- 导出一:通过 module.exports;
- 导出二:通过 exports.a = function() {};
- 引入:require(path),
const a = require('./a.js')
; - 优点:
- 拥有独立作用域;
- 依赖关系处理简单;
浏览器:amd,异步模块定义
- 定义模块:define(id?, depend?, factory)
- 工厂函数会提供三个参数:
- require
- exports
cmd,类似于 amd
umd:处理各种模块化之间的差异
es6:esm-ECMAScript module
使用:
<script type='module'></script>
导出一:export default 模块,默认导出;
引入一:import 模块名 from 模块路径,默认导入;
要求必须在服务器环境下,才能运行
导出二:
export 模块;
,多导出;引入二:
import {a as A} from './a.js';
,模块名必须等同于导出模块名,可以通过 as 重命名;esm,IE8、IE9 不支持,这是引入了 webpack,模块打包器。
webpack
- webpack:核心代码;
- webpack-cli:cli 命令行界面;
正常目录解读
- src:js 代码,逻辑代码;
- public:html 存放目录;
使用
- webpack 只认识相对路径;
没有配置时
- 可以在控制台,执行
node_modules/.bin/webpack ./src/index.js
;
自定义配置
创建 webpack.config.js;
webpack 会运行到 node 环境下面,所有使用
module.exports = {};
;entry:入口文件;
-
output:输出目录,是一个对象;,里面路径接受绝对路径;
// 一对一 const { resolve } = require("path"); module.exports = { // 入口文件 entry: "./src/index.js", // 输出目录 output: { // 接受绝对路径 path: resolve(__dirname, "build"), filename: "index.js", }, };
// 多对一 const { resolve } = require("path"); module.exports = { // 入口文件 entry: [ './src/index.js', './src/b.js' ], // 输出目录 output: { // 接受绝对路径 path: resolve(__dirname, "build"), filename: "index.js", }, };
// 多对多 entry: { index: "./src/index.js", b: "./src/b.js" }, // 输出目录 output: { // 接受绝对路径 path: resolve(__dirname, "dist"), filename: "[name].js", clean: true, },
-
mode:production、development、none
- 默认 production;
webpack 会将任意文件数据作为模块进行处理;
当 webpack 遇到不能解析的模块时,webpack 会找到 module 对象下面的 rules,去匹配对应规则;如果有对应规则匹配时,我们就使用对应的 loader 去解析;
-
module
module: { rules: [ { test: /\.txt$/, use: "raw-loader", }, { test: /\.(png|gif|jpe?g)$/, use: { loader: "file-loader", options: { // 图片打包后文件名 name: "[name]_[contenthash].[ext]", // 打包后存放目录,相对于打包文件夹 dist outputPath: './images', // 使用时,获取的是绝对路径 // 打包后存放目录,相对于 dist 文件 publicPath: "./images", // 使用时,引用的是相对路径 ./dist/images/redux_xxx.jpg // url-loader,多一个 limit,number 类型; // 当图片小于此值时,url-loader 不会对文件进行打包,而是转为 base64 格式,引入到 js 文件中 // limit: 10240, }, }, }, { test: /\.css$/, // loader 执行顺序:从下至上,从右至左 use: [ "style-loader", { loader: "css-loader", options: { // 启用 url url: true, // 启用 @import import: true, // 启用 sourcemap sourceMap: false, }, }, ], }, ], }
// 自定义 loader module.exports = { // ... // 配置 loader 解析路径,当自定义 loaders 时,需要使用 // 如在根目录下创建 loaders 来存放自定义 loader resolveLoader: { modules: ["node_modules", resolve(__dirname, "./loaders")], }, module: { rules: [ { test: /\.doc$/, use: "doc-loader", }, ], }, }
const MarkDown = require('markdown-it'); const md = new MarkDown(); module.exports = (source) => { console.log('source: ', md.render(source)); // return a Buffer or String,返回的结果会直接 eval return `export default \`${md.render(source)}\``; }
- webpack5 之后,loader 变更
- asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。 - asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。 - asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
-
plugin:都是以类或者构造函数的形式出现
- clean-webpack-plugin
// CleanWebpackPlugin // webpack4.0 const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // ... plugins: [ new CleanWebpackPlugin(), ] // webpack5.0 output: { // 接受绝对路径 path: resolve(__dirname, "dist"), filename: "main.js", clean: true, },
- html-webpack-plugin:自动生成一个 html 文件,与打包后的 js 文件相关联
- mini-css-extract-plugin:
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // ... module: { rules: [ { test: /\.css$/, // loader 执行顺序:从下至上,从右至左 use: [ // "style-loader", // 将 css 样式添加到 head 当中的 style 里面;如果样式很多时,导致html文件过大,同时也不能对 css 进行优化,这时可以用 mini-c's's MiniCssExtractPlugin.loader, "css-loader", // 处理 css 文件 ], }, { test: /\.doc$/, use: "doc-loader", }, ], }, // ... plugins: [ new MiniCssExtractPlugin({ filename: '[name].css' }), ],
- clean-webpack-plugin
-
devServer
devServer: { port: 9000, open: true, // 自动开启浏览器 proxy: { // 请求地址:服务器地址 // '/api' : 'http://localhost:3000' 'api': { target: 'http://localhost:3000', pathRewrite: {'^/api': ''}, secure: false, } }, hot: true, // 默认开启 }
-
devtool:开发模式下,方便调试用
// source-map,要严格遵循下列顺序:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map devtool: 'eval-cheap-source-map',
webpack-dev-server
- webpack4.0:在 package.json 中添加执行命令
dev: webpack-dev-server
; - webpack5.0:在 package.json 中添加执行命令
dev: webpack serve
- 启动开发服务器,打包后生成的文件,会存到内存中,不会写入硬盘,这样可以提高开发效率
代码分割
1.最基本的代码分割
// 多对多打包方式,可能会导致同一模块打包进多个 chunk
entry: {
index: {
import: "./src/index.js",
dependOn: ["axios", "a"]
},
b: {
import: "./src/b.js",
dependOn: ["axios", "a"]
},
axios: "axios",
a: "./src/a.js"
},
// 当你选择使用代码分割时,webpack可能会受[启发式]的在多个chunk中复制依赖模块。如果发生了这种事情,这时您有可能得到相同模块的多个实例,他们之间的状态将会很难保证同步。当然,您可以通过设置 `optimization.runtimeChunk` 为 `“single”` 进行解决。这并不能阻止Webpack在入口点之间复制模块代码,但它可以防止Webpack在运行时创建同一模块的两个实例。
optimization: {
// 配置 runtimeChunk 为 single,webpack 会帮助生成一个 runtime chunk,然后他会把加载的包移到 runtime 中
runtimeChunk: "single"
},
2.SplitChunksPlugin
- CommonsChunkPlugin 曾被用来避免他们之间的重复依赖,但是不可能再做进一步的优化。
- 从 webpack v4 开始,移除了 CommonsChunkPlugin,取而代之的是 optimization.splitChunks。
entry: {
index: "./src/index.js",
b: "./src/b.js",
},
optimization: {
splitChunks: {
// async 表示只从异步加载模块中进行拆分
// initial 表示只从入口模块进行拆分
chunks: 'all'
}
},
- 默认配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
动态导入
// 异步加载 按需加载
// 使用 import() 函数时,会返回一个 promise 对象
// 内联注释 magic comments,需要按照 json 格式编写
import(
/* webpackChunkName: "my-chunk-name" */
'axios'
).then(res => {
console.log('axios: ', res);
})
预加载 / 预获取
- 其实是针对于动态导入资源加载优化
- preload:预加载,当我们配置时,被配置的模块会在父 chunk 加载时并行加载该模块;
- prefetch:预获取,当我们配置时,被配置的模块会在浏览器闲置时,也就是未来某个时刻下载模块
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackPrefetch: true */
/* webpackPreload: true */
'axios'
).then(res => {
console.log('axios: ', res);
})
外部扩展
- 防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
- key:是我们需要进行外部扩展的包名,后续打包时不会将该包打包;
- value:是该库声明的全局变量;
module.exports = {
//...
externals: {
jquery: 'jQuery',
},
};
tree shaking
- 将上下文中的dead-code移除,就像摇树上的枯叶使其掉落一样;
optimization: {
usedExports: true,
},
mode: 'production',
webpack.config.js
const { resolve } = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
// 最基本的代码分割
// 多对多打包方式,可能会导致同一模块打包进多个 chunk
entry: {
index: "./src/index.js",
b: "./src/b.js",
},
// 当你选择使用代码分割时,webpack可能会受[启发式]的在多个chunk中复制依赖模块。如果发生了这种事情,这时您有可能得到相同模块的多个实例,他们之间的状态将会很难保证同步。当然,您可以通过设置 `optimization.runtimeChunk` 为 `“single”` 进行解决。这并不能阻止Webpack在入口点之间复制模块代码,但它可以防止Webpack在运行时创建同一模块的两个实例。
// optimization: {
// // runtimeChunk: "single"
// splitChunks: {
// // async 表示只从异步加载模块中进行拆分
// // initial 表示只从入口模块进行拆分
// chunks: 'all'
// }
// },
// 输出目录
output: {
// 接受绝对路径
path: resolve(__dirname, "dist"),
filename: "[name].js",
clean: true,
},
mode: "development",
// 配置 loader 解析路径,当自定义 loaders 时,需要使用
// 在根目录下创建 loaders 来存放自定义 loader
resolveLoader: {
modules: ["node_modules", resolve(__dirname, "./loaders")],
},
// loader 使用
module: {
rules: [
{
test: /\.txt$/,
use: "raw-loader",
},
{
test: /\.(png|gif|jpe?g)$/,
use: {
loader: "file-loader",
options: {
// 图片打包后文件名
name: "[name]_[contenthash].[ext]",
// 打包后存放目录,相对于打包文件夹 dist
outputPath: "./images", // 使用时,获取的是绝对路径
// 打包后引用目录
publicPath: "./images", // 使用时,引用的是相对路径 ./images/redux_xxx.jpg
// url-loader,多一个 limit,number 类型;
// 当图片小于此值时,url-loader 不会对文件进行打包,而是转为 base64 格式,引入到 js 文件中
// limit: 10240,
},
},
},
{
test: /\.css$/,
// loader 执行顺序:从下至上,从右至左
use: [
// "style-loader", // 将 css 样式添加到 head 当中的 style 里面;如果样式很多时,导致html文件过大,同时也不能对 css 进行优化,这时可以用 mini-c's's
MiniCssExtractPlugin.loader,
"css-loader", // 处理 css 文件
],
},
{
test: /\.doc$/,
use: "doc-loader",
},
],
},
// 注册插件的位置,plugins 的使用
// webpack 会自动调用对应插件的 apply 方法,把插件注册到 webpack 对应的生命周期当中
// 插件 是 webpack 的 支柱 功能。Webpack 自身也是构建于你在 webpack 配置中用到的 相同的插件系统 之上
// 插件目的在于解决 loader 无法实现的其他事。Webpack 提供很多开箱即用的 插件。
plugins: [
// new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "app.html", // 会自动创建一个纯净的 app.html 文件
template: "./public/index.html", // 会把已有 html 文件中的信息带上
title: "app", // <title><%=htmlWebpackPlugin.options.title%></title>
}),
new MiniCssExtractPlugin({
filename: '[name].css'
}),
],
devServer: {
port: 9000,
open: true, // 自动开启浏览器
proxy: {
// 请求地址:服务器地址
// '/api' : 'http://localhost:3000'
'api': {
target: 'http://localhost:3000',
pathRewrite: {'^/api': ''},
secure: false,
}
},
hot: true, // 默认开启
},
// source-map,要严格遵循下列顺序:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
devtool: 'eval-cheap-source-map',
};