2021/07/28 小雨
为什么会使用到 webpack 呢?原因是这样的,最近在学习 Three.js 的过程中通过 script
标签引入相关依赖过于冗杂,使用起来也很不方便,如下所示:
<script src="../../../lib/jquery-1.12.min.js"></script>
<script src="../../../lib/three.min.js"></script>
<script src="../../../lib/libs/dat.gui.min.js"></script>
<script src="../../../lib/libs/inflate.min.js"></script>
<script src="../../../lib/libs/NURBSCurve.js"></script>
<script src="../../../lib/controls/OrbitControls.js"></script>
<script src="../../../lib/loaders/FBXLoader.js"></script>
<script src="../../../lib/libs/inflate.min.js"></script>
<script src="../../../lib/postprocessing/EffectComposer.js"></script>
<script src="../../../lib/postprocessing/ShaderPass.js"></script>
<script src="../../../lib/shaders/CopyShader.js"></script>
<script src="../../../lib/shaders/LuminosityHighPassShader.js"></script>
<script src="../../../lib/postprocessing/UnrealBloomPass.js"></script>
<script src="../../../lib/postprocessing/RenderPass.js"></script>
<script src="../../../lib/renderers/CSS3DRenderer.js"></script>
<script src="../../../lib/libs/Tween.js"></script>
<script src="./js/seat.js"></script>
于是我就想在这种没有使用类似 vue 、react 这种工程化的项目中如何去使用 npm 来管理各种依赖,首先找到的方法是在 script
标签上加上 type="module"
以此来启用模块功能,然后就能使用 import
来引入依赖了,如下所示:
<script type="module">
import * as Three from '../../node_modules/three/build/three.module.js'
</script>
但是之后又出现了一个问题,当我使用import
加载 three 相关插件的时候,浏览器会产生下面的报错:
Uncaught TypeError: Failed to resolve module specifier "three". Relative references must start with either "/", "./", or "../"
经过一番检查之后,发现原来通过 npm 安装的插件引入依赖时 import
使用的是工程化的路径,如果不想进入每个子模块插件修改相对路径,就只能使用打包工具来解决这一问题了。
webpack 是一个用于 JavaScript 应用程序的静态模块打包工具。它可以将各类资源整合到一起,包括.css、.png、.js
等文件,然后“打包”生成一个新的 bundle 文件。新的 js 文件仅包含使用到的依赖,并且可压缩代码,提高运行速度。
webpack 有这么几个主要的概念:entry
、output
、loader
、plugin
、mode
entry
用于在配置文件中配置打包入口。常用格式如下:
module.exports = {
entry: './entry/file.js'
};
output
用于在配置文件中配置打包后的输出位置。常用格式如下:
path
表示输出文件的绝对路径,__dirname
表示该配置文件所在的绝对路径,filename
表示输出的新文件名,path.resolve()
会将括号内的参数解析成一个完整的路径,如下会打包文件就会输出到__dirname/dist/my-first-webpack.bundle.js
const path = require('path');
module.exports = {
entry: './entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'myFirst.bundle.js'
}
};
loader
主要用以使 webpack 能够加载打包非 js 文件。所有的 loader
可以在官网查看。常用格式如下:
npm install --save-dev style-loader css-loader
const path = require('path');
module.exports = {
entry: './entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'myFirst.bundle.js'
},
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
};
plugin
插件功能是 webpack 的支柱功能,主要是为了解决 loader
无法实现的其他情况,例如 HtmlWebpackPlugin
插件会在打包完成后自动生成一个 html 文件并且引入对应的 bundle 。常用格式如下:
npm install --save-dev html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './entry/file.js',
plugins: [
new HtmlWebpackPlugin(),
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'myFirst.bundle.js'
},
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
};
mode
用来在配置文件中指定环境: development
& production
。 webpack 在不同环境下会加载不同的插件和处理方式。
module.exports = {
mode: "production",
}
module.exports = {
mode: "development",
}
首先在项目里安装一下 webpack :
npm install webpack webpack-cli --save-dev
创建配置文件 webpack.config.js ,具体配置内容可以参考上方相关概念
const path = require('path');
module.exports = {
entry: 入口文件,
output: {
path: 出口文件目录,
filename: 出口文件名
},
module: {
rules: [
各类loader配置项
]
},
plugins: [各类插件名],
};
运行打包命令
npx webpack --config webpack.config.js
用 cli 这种方式来运行本地的 webpack 并不是特别方便,我们也可以在 package.json 中设置一个快捷方式
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
然后就可以使用如下的命令来启动 webpack 打包
npm run build
在开发过程中,如果我们修改一个地方就需要进行一次打包的话那样效率就太低了, webpack 也提供了一个本地服务 webpack-dev-server
具有实时重载的功能,具体使用方法也很简单,如下所示:
首先进行一下安装
npm install --save-dev webpack-dev-server
修改配置文件,告知 dev server,从什么位置查找文件:
关于模块热替换,可以前往官网了解,这里不做赘述
module.exports = {
devServer: {
contentBase: './dist', // 文件对应位置
hot: true // 是否开启模块热替换
}
}
运行方式添加至 package.json
"scripts": {
"start": "webpack serve --open",
},
运行
npm start
以下内容需要先学习一下 webpack 的管理输出相关指南
webpack 默认的打包规则,只能存在一个入口一个出口,但是我这个是练习使用的 demo ,想要让打包出来的文件如下图所示:
于是经过一番搜索之后,发现了 glob
这个插件,它可以帮助我匹配文件夹中所有符合条件的文件,首先我先实现了多入口,代码如下所示:
npm i glob -D
const path = require('path');
const glob = require('glob');
module.exports = {
mode: 'development',
entry: glob.sync("./基础知识/*/*.js").reduce((entries, p) => {
const name = path.basename(p, '.js') //获取路径的文件名 aaa/bbb.js => bbb
return {
...entries,
[name]: p
}
}, {}),
...
}
多出口可以通过 [name]
这个变量实现
output: {
path: path.resolve(__dirname, "基础知识/dist"),
filename: "[name]/[name].bundle.js",
},
同理我也可以通过插件 HtmlWebpackPlugin
为每个出口 bundle 配置对应的 html ,整体配置如下所示:
const path = require("path");
const glob = require("glob");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const EncodingPlugin = require("webpack-encoding-plugin");
module.exports = {
mode: "development",
entry: glob.sync("./基础知识/*/*.js").reduce((entries, p) => {
const name = path.basename(p, ".js"); //获取路径的文件名 aaa/bbb.js => bbb
return {
...entries,
[name]: p,
};
}, {}),
devtool: "inline-source-map",
plugins: [
...glob.sync("./基础知识/*/*.js").reduce((entries, p) => {
const name = path.basename(p, ".js"); //获取路径的文件名 aaa/bbb.js => bbb
return [
...entries,
new HtmlWebpackPlugin({
chunks: [name],
template: "./基础知识/" + name + "/" + name + ".html",
filename: name + "/" + name + ".html",
}),
];
}, []),
new CleanWebpackPlugin(),
new EncodingPlugin({
encoding: "UTF-8",
}),
],
output: {
path: path.resolve(__dirname, "基础知识/dist"),
filename: "[name]/[name].bundle.js",
clean: true,
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ["file-loader"],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ["file-loader"],
},
],
},
};
我的原本主要目的就是想减少 script 标签冗杂的引入,却没想到使用 webpack 尝试了一下模块化项目的构建过程,学习的过程就是这么有趣。