实用 WEBPACK 基础:纯 Javascript 前端
内容
灵感
大约两年前,我创建了一个 Webpack 配置,以满足我使用 React 开发的特定需求。从那时起,我主要负责大型项目,而且因为我用过 React,所以从不需要重新学习。最近,我决定做一些小实验,发现创建一个纯 JavaScript 的 Webpack 配置有点困难。我不想在没有真正理解它的作用的情况下照搬别人的。所以,在深入研究之后,我创建了一个易于理解、实用的指南,如果我最终忘记了需要再回来看的话,也许它还能帮我解决😅。
先决条件
首先你需要Node、npm和最好是npx,如果你已经安装了这些,你可以跳到下一部分(下一部分)
安装 Node
选项 A:(推荐 NVM(节点版本管理器)
通常建议使用 nvm 来安装和管理 Node 的各个版本。您可以在此处查看针对您的操作系统的安装说明。如果可以,请务必使用上面的链接;如果不行,您可以尝试运行这些……
通过 curl 安装curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
重新加载终端source ~/.bashrc
检查安装nvm -v
使用 nvm 安装一个版本的节点(例如 16)nvm install 16
或
使用 nvm 安装最新版本的节点nvm install node
使用 nvm 来使用它安装的 Node 版本(例如 16)nvm use 16
选项 B:直接安装
您可以访问此处获取针对您的特定操作系统的安装说明。
npm 和 npx
npm 和 npx 通常与 node 一起安装,您可以使用npm --version
和 进行测试npx --version
。
注意:Node、npm和npx是完全不同的概念。Node是执行环境(基本上就是运行代码的那个东西);npm ,即 Node 包管理器,用于管理node的包;npx ,即 Node 包执行器,允许我们运行已安装的node包。这些概念的版本(大部分)是独立的,因此,当你运行或或 时,不要期望看到相同的版本号。npm --version
node --version
npx --version
根据您选择的选项, npx可能未安装,因此您可以运行以下命令:
全局安装 npx(如果已经安装了 npx,请不要运行,再次检查npx --version
)npm install -g npx
设置
为我们的教程创建一个文件夹,比如说sample-frontend
,进入sample-frontend
并通过运行初始化该文件夹npm init
,这将带您回答一些问题来设置您的项目。
完成后,您可以创建一个sample_index.html
文件,然后创建一个src
文件夹,并在里面src
创建一个sample_index.js
文件。
现在你的文件夹看起来应该是这样的
sample-frontend
|- package.json
|- sample_index.html
|- /src
|- sample_index.js
示例文件
sample_index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
I am body !
<script type='text/javascript' src="./src/sample_index.js"></script>
</body>
</html>
sample_index.js
window.onload = ev => {
init();
}
const init = () => {
console.log('hello I am a sample index.js');
}
通过在浏览器上访问 //sample_index.html 检查您的页面并确保一切正常。
Webpack 基础知识
Webpack 是什么?根据官方文档
Webpack 的核心是一个用于现代 JavaScript 应用程序的静态模块打包器。当 Webpack 处理你的应用程序时,它会在内部从一个或多个入口点构建一个依赖关系图,然后将项目所需的每个模块组合成一个或多个包,这些包是用于提供内容的静态资源。
概括和简化(对于初学者来说)
Webpack 查看您的所有代码并输出单个内容。
你可能会问以下问题:(1)它是如何工作的(2)它输出什么(3)它有什么用处
它如何工作以及它输出什么
让我们安装 webpack 并找出答案。
安装 webpack 及其 clinpm install --save-dev webpack webpack-cli
导航到项目的根目录sample_project
并运行以下命令:npx webpack --entry ./src/sample_index.js --mode production
这个命令和它的输出告诉我们很多信息。它说请从这里开始查看我的代码,sample_index.js
并输出一些可以投入生产的内容。
如果我们查看我们的项目目录,我们会看到一个文件夹dist
和里面的一个文件main.js
。
sample-frontend
|- package.json
|- package-lock.json
|- sample_index.html
|- /src
|- sample_index.js
|- /dist
|- main.js
|- /node_modules...
main.js
应该包含类似这样的内容(()=>{window.onload=l=>{o()};const o=()=>{console.log("hello I am a sample index.js")}})();
main.js是我们代码库的精简和混淆版本。(精简 - 通过从代码中删除不必要的字符来缩短加载时间;混淆 - 通过使其难以阅读/理解来保护您的代码/逻辑)
我们可以编辑我们的sample_index.html
使用方法main.js
,它将以相同的方式工作。
sample_index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
I am body !
<!-- <script type='text/javascript' src="./src/sample_index.js"></script> -->
<script type='text/javascript' src="./dist/main.js"></script>
</body>
</html>
(您可以通过在浏览器中访问您的页面进行测试)
还有一些其他的事情要做(其实是最重要的😅),但我们需要添加依赖项才能看到它的实际效果。我们将添加两个依赖项,一个是我们安装的 Node 包,另一个是我们自己的,我们将使用它们来构建一个艺术品搜索应用。
示例应用程序和捆绑
注意:由于本教程不涉及代码编写,因此应用程序工作原理的解释将在文件本身的注释中记录。
让我们添加第一个依赖项,一个帮助我们发出请求的包。npm install --save axios
现在我们自己,在src
创建一个文件art_helper.js
。
sample-frontend
|- package.json
|- package-lock.json
|- sample_index.html
|- /src
|- sample_index.js
|- art_helper.js
|- /dist
|- main.js
|- /node_modules...
编辑art_helper.js以包含以下内容:
import Axios from 'axios';
export const searchArt = ({ title }) => {
const params = { q: title, limit: 5, fields: 'id,title,image_id,artist_display' }; // sample set of params, limits the number of results to 5, and only returns the id, title, image_id, and artist_display fields
return Axios.request({
url: 'https://api.artic.edu/api/v1/artworks/search',
params
}).then(res => {
const { config, data } = res.data;
const artPieces = data.map(artPiece => ArtPiece({ config, ...artPiece })); // map the data to an array of HTML strings where each HTML string is an art piece (see ArtPiece function below)
return { artPieces };
}).catch(err => {
console.log(err);
});
}
// Takes a config and the data for an art piece and returns an HTML string
const ArtPiece = ({ config, title, image_id, id, artist_display }) => {
const image_url = `${config.iiif_url}/${image_id}/full/843,/0/default.jpg`;
console.log(image_url);
return (`<div class='art_piece'>
<img src="${image_url}" />
<h3>${title}</h3>
<p>- ${artist_display}</p>
</div>`);
}
/**
* Fetches art from The Art Institute of Chicago API ( https://api.artic.edu/docs )
* First queries the API for art pieces with the given title ( https://api.artic.edu/api/v1/artworks/search )
* Then returns an array of ArtPiece objects
* The image URL for each ArtPiece is constructed from the IIIF URL ( config.iiif_url ) and the image_id
*/
编辑您的sample_index.html
内容以反映以下内容:
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id='root'>
<input type='text' id='title-search-input'/>
<button id='title-search-button'>Search</button>
<div id='search-results'>
</div>
</div>
<!-- <script type='text/javascript' src="./src/sample_index.js"></script> -->
<script type='text/javascript' src="./dist/main.js"></script>
</body>
</html>
最后编辑sample_index.js
以下内容:
import { searchArt } from './art_helper';
window.onload = ev => {
init();
}
const init = () => {
console.log('hello I am a sample index.js vn');
document.getElementById('title-search-button').addEventListener('click', handleSearchClick);
}
const handleSearchClick = (ev) => {
const title = document.getElementById('title-search-input').value;
searchArt({ title }).then(renderResults);
}
const renderResults = ({ artPieces }) => {
document.getElementById('search-results').innerHTML = artPieces.join('');
}
再次在项目根目录下运行
npx webpack --entry ./src/sample_index.js --mode production
然后刷新页面并尝试一下。
现在,我们已经有了一个基本的艺术品搜索应用,需要注意以下几点:我们无需在sample_index.html
依赖项art_helper.js和Axios中使用 script 标签,而且也无需担心它们的加载顺序。此外,如果我们查看main.js文件,虽然它经过了压缩和混淆处理,难以阅读,但我们会发现 ES6+ 语法已被向下翻译,这使我们能够使用所有 ES6+ 代码,而无需担心跨浏览器的兼容性问题。
从我们的入口点开始,sample_index.js
我们的代码已经被彻底解析,同时构建了我们精确依赖关系的图表,以捆绑、转换(ES6 + JS 到浏览器兼容版本)、混淆和最小化为main.js
我们可以使用的一个文件。
配置
运行快捷命令固然没问题,但当我们想要指定并执行更多操作时,配置文件就变得必不可少了。尤其是在与他人协作时,调试配置文件比调试冗长的命令更容易。让我们将构建命令转换为配置,在项目目录中sample-frontend
创建一个文件webpack.config.js
。
sample-frontend
|- package.json
|- package-lock.json
|- sample_index.html
|- webpack.config.js
|- /src
|- sample_index.js
|- art_helper.js
|- /dist
|- main.js
|- /node_modules...
编辑 webpack.config.js 以反映以下内容:
const path = require('path');
module.exports = {
entry: {
main: './src/sample_index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
},
mode: 'production'
}
现在您可以运行npx webpack --config webpack.config.js
并获得相同的结果。
这里发生了什么?我们正在导出一个对象,它描述了我们想要发生的事情。
入口
Webpack 应该从哪里启动,可以改写为“我的应用程序从哪里启动?”,在本例中为“./src/sample_index.js”。
你会注意到,entry 是一个对象(一组键值对),我们将入口点命名为“main”,这是因为默认情况下,打包后的 JavaScript 文件的名称是“main”。不过,我们可以随意重命名,一个通俗易懂的名称或术语就是“bundle”。
const path = require('path');
module.exports = {
entry: {
bundle: './src/sample_index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
},
mode: 'production'
}
如果您npx webpack --config webpack.config.js
现在在dist文件夹中运行,您将看到两个文件main.js
和bundle.js
。现在我们面临两个问题:(1) 如果我们生成一个名称不同的文件,旧文件会保留;(2) 我们的目录需要更新才能加载新的文件名。我们可以通过配置配置对象outputsample_index.html
的下一部分来解决其中一个问题。
输出
在 output 中,我们描述了希望 webpack 输出的内容(是不是有点疯狂?)。目前我们只有一个规范,即路径,也就是我们希望打包后的文件输出到哪里。path.resolve(__dirname, 'dist')
这意味着获取当前目录的绝对路径,然后输出到dist文件夹。
现在,在重新配置上面的entry之后,我们发现以不同名称生成的旧文件仍然残留在那里。为了解决这个问题,我们必须告诉 webpack 在我们之后进行清理。
const path = require('path');
module.exports = {
entry: {
bundle: './src/sample_index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
mode: 'production'
}
现在运行程序,npx webpack --config webpack.config.js
我们会看到它main.js
已被移除,但还有一个问题:每当打包文件名发生变化时,我们都必须进行编辑sample_index.html
。为了解决这个问题,我们转到了一个新的插件部分
插件
插件就像名称所暗示的那样,可以添加各种各样的功能,在这种情况下,我们想告诉 webpack 为我们生成一个 index.html 文件,并自动将我们生成的包注入到这个文件中。
首先我们需要安装所需的插件html-webpack-plugin
npm install --save-dev html-webpack-plugin
现在我们可以编辑webpack.config.js
以使用html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
bundle: './src/sample_index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './sample_index.html',
filename: 'index.html'
})
],
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
}
}
如上所示,plugins 接受一个插件对象列表,我们只有一个,那就是 html-webpack-plugin,它配置为使用我们的sample_index.html
作为模板,创建一个名为的新文件,index.html
该文件将包含一个脚本标签,用于正确链接生成的任何包。这意味着我们必须编辑我们的sample_index.html
,并删除手动执行的操作。
sample_index.html
现在变成
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id='root'>
<input type='text' id='title-search-input'/>
<button id='title-search-button'>Search</button>
<div id='search-results'>
</div>
</div>
</body>
</html>
现在,如果我们运行,npx webpack --config webpack.config.js
我们将看到我们的 javascript 包和 index.html 文件dist
。现在您可以index.html
在浏览器中打开并测试它。
发展
到目前为止,我们一直在手动刷新页面并重新打开 html 文件,这太荒谬了,让我们解决这个问题。与其使用其他工具,创建复杂的流程,先用一个工具构建,再用另一个工具进行实时热加载,还好 Webpack 在同一个生态系统中提供了一个解决方案webpack-dev-server
。
让我们安装webpack-dev-server
npm install --save-dev webpack-dev-server
现在将当前配置复制到名为的新文件中webpack.dev.config.js
,并重命名旧文件webpack.prod.config.js
sample-frontend
|- package.json
|- package-lock.json
|- sample_index.html
|- webpack.dev.config.js
|- webpack.prod.config.js
|- /src
|- sample_index.js
|- art_helper.js
|- /dist
|- main.js
|- /node_modules...
编辑webpack.dev.config.js
以反映以下内容:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
bundle: './src/sample_index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './sample_index.html',
filename: 'index.html'
})
],
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
devtool: 'inline-source-map', // allows error messages to be mapped to the code we wrote and not compiled version
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 3000,
host: 'localhost',
hot: true,
open: true
}
}
如您所见,我们在配置中添加了一个devServer部分,其中描述了我们希望开发服务器如何运行。首先,static
它告诉它在哪里找到我们的代码,即代码构建到的位置,host
并且port
含义不言自明;设置hot
为 true 可启用热重载(保持应用程序运行并在运行时注入您编辑的文件的新版本);最后,设置open
为 true 将在运行此配置时打开浏览器。
我们还添加了一个 devtool inline-source-map,它将输出(日志和错误)映射到源代码而不是捆绑包,这样我们就可以实际使用我们的控制台进行调试。
现在我们有两种不同的配置:dev 和 prod
我们可以通过运行以下命令来启动开发服务器
npx webpack serve --config webpack.dev.config.js
您的浏览器应该会在 localhost:3000 上打开,并且您的应用正在运行。现在,无论何时编辑 JavaScript 文件,应用都会自动重新加载并显示更改。
请注意,我特意提到了 JavaScript 文件,这是因为 Webpack 只能识别 JavaScript 和 JSON 文件。这就是为什么我们没有使用任何 CSS 的原因。例如,我们可以通过常规方式(为其添加标签)使用 CSS,但这样会失去 Webpack 在构建输出时提供的优化。为了利用文件,我们需要使用另一个模块部分。
模块和加载器
Webpack 默认只接受 JavaScript 和 JSON 文件,加载器改变了这一点。它通过添加加载和处理这些其他文件类型的功能来实现这一点,几乎将每个文件都视为另一个 JavaScript模块。将这些其他文件(例如 CSS 文件)视为模块的功能允许 Webpack 将它们添加到依赖关系图中,从而优化构建。
与插件类似,存在各种适用于各种文件类型和功能的加载器。我们需要安装一个与插件类似的加载器,然后在 Webpack 配置中进行设置。
让我们添加两个加载器来帮助我们处理 CSS
npm install --save-dev style-loader css-loader
现在让我们在配置中使用它们
第一的webpack.dev.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
bundle: './src/sample_index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './sample_index.html',
filename: 'index.html'
})
],
module: {
rules: [
{ test: /\.css$/, use: ['style-loader','css-loader'] }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
devtool: 'inline-source-map', // allows error messages to be mapped to the code we wrote and not compiled version
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 3000,
host: 'localhost',
hot: true,
open: true
}
}
然后webpack.prod.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
bundle: './src/sample_index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './sample_index.html',
filename: 'index.html'
})
],
module: {
rules: [
{ test: /\.css$/, use: ['style-loader','css-loader'] }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
}
如您所见,我们添加了一个模块部分,其中定义了要读取哪些文件以及如何在这些文件上使用加载器的规则。每个规则 test
的两个部分和use
分别告诉我们要查看哪些文件以及要使用哪些加载器。我们的规则test
基本上是使用任何以“.css”结尾的文件,并使用style-loader
和css-loader
来处理这些文件。规则和加载器可能有点棘手,例如,它按照它们在配置中包含的顺序执行,而不同的顺序可能会产生截然不同的结果。
这里有一些关于加载器的更多文档,这里是一些加载器的列表。
让我们添加一些风格,创建style.css
一个src
sample-frontend
|- package.json
|- package-lock.json
|- sample_index.html
|- webpack.dev.config.js
|- webpack.prod.config.js
|- /src
|- sample_index.js
|- art_helper.js
|- style.css
|- /dist
|- main.js
|- /node_modules...
tbh ,你可以做任何你喜欢的事情style.css
:P,但如果你很忙的话,这里有一些快速的方法
#title-search-input {
width:50vw
}
#title-search-button {
width:30vw
}
#search-results{
width: 100%;
text-align: center;
}
.art_piece{
width: 100%;
margin: 30px 30px 30px 30px;
padding:3%;
-webkit-box-shadow: 0px 10px 18px 2px rgba(0,0,0,0.67);
box-shadow: 0px 10px 18px 2px rgba(0,0,0,0.67);
}
(编辑:感谢 cswalker21 提醒我展示这一步)
最后编辑sample_index.js
并导入style.css
类似
import './style.css';
我绝对不是 CSS 方面的高手,所以我鼓励你尝试一下自己的东西。
现在,我们来运行一下开发服务器,看看它是什么样子。
npx webpack serve --config webpack.dev.config.js
我们还可以测试我们的构建
npx webpack --config webpack.prod.config.js
现在您已准备好开始自行构建和探索,以下只是一些额外的内容。
额外比特
命令
如果您想让自己或团队中的事情变得更简单一些,您可以使用scripts
您的部分package.json
(又名 npm sripts)为您的命令创建一个“快捷方式”。
例如,npx webpack serve --config webpack.dev.config.js
我们不必总是编写启动我们的开发服务器,而是可以创建一个“快捷命令”,例如npm run start
通过编辑“脚本”部分并添加一个值为的键“start” npx webpack serve --config webpack.dev.config.js
。
{
...
"scripts":{
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx webpack serve --config webpack.dev.config.js"
}
...
}
所以现在我们可以运行npm run start
来启动我们的开发服务器
我们也可以做一些类似的事情来构建
{
...
"scripts":{
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx webpack serve --config webpack.dev.config.js",
"build": "npx webpack --config webpack.prod.config.js"
}
...
}
现在我们可以运行npm run build
来构建我们的应用程序
TypeScript
一直想尝试 typescript,但不知道如何将其转换为 javascript 进行开发甚至构建,让我们尝试一下吧。
让我们安装所需的 TypeScript 本身和 Webpack 的加载器
npm install --save-dev typescript ts-loader
现在让我们添加一条规则来加载 .ts ( typescript )文件,并告诉 webpack 我们需要解析哪些类型的文件(模块解析处理在构建时在哪里找到什么)。(参见模块解析)
首先webpack.dev.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
bundle: './src/sample_index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './sample_index.html',
filename: 'index.html'
})
],
module: {
rules: [
{ test: /\.css$/, use: ['style-loader','css-loader'] },
{ test: /\.ts$/, use: 'ts-loader', exclude: /node_modules/ }
]
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
devtool: 'inline-source-map', // allows error messages to be mapped to the code we wrote and not compiled version
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 3000,
host: 'localhost',
hot: true,
open: true
}
}
然后webpack.prod.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
bundle: './src/sample_index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './sample_index.html',
filename: 'index.html'
})
],
module: {
rules: [
{ test: /\.css$/, use: ['style-loader','css-loader'] },
{ test: /\.ts$/, use: 'ts-loader', exclude: /node_modules/ }
]
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
}
最后,我们需要添加一个tsconfig.json
(sample-frontend
tsconfig文档)来描述我们的 TypeScript 应该如何编译
sample-frontend
|- package.json
|- package-lock.json
|- sample_index.html
|- webpack.dev.config.js
|- webpack.prod.config.js
|- tsconfig.json
|- /src
|- sample_index.js
|- art_helper.js
|- style.css
|- /dist
|- main.js
|- /node_modules...
tsconfig.json
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"allowJs": true,
"moduleResolution": "node"
}
}
恭喜现在我们可以编写 TypeScript,例如创建validation_helper.ts
并src
添加以下内容:
export const validateSearchQuery = (title: string, limit: number, offset: number) => {
if(title.length == 0)
throw new Error("Title is required");
if(limit < 0)
throw new Error("Limit must be greater than 0");
if(offset < 0)
throw new Error("Offset must be greater than 0");
if(limit > 10)
throw new Error("Limit must be less than 10");
return true;
}
现在您可以validadateSearchQuery
从中导入validation_helper.ts
并使用它来很好地验证您的查询
我们可以编辑sample_index.js
以下内容:
import { searchArt } from './art_helper';
import { validateSearchQuery } from './validation_helper';
import './style.css';
window.onload = ev => {
init();
}
const init = () => {
console.log('hello I am a sample index.js v5');
document.getElementById('title-search-button').addEventListener('click', handleSearchClick);
}
const handleSearchClick = (ev) => {
const title = document.getElementById('title-search-input').value;
validateSearchQuery(title, 5, 10);
searchArt({ title }).then(renderResults);
}
const renderResults = ({ artPieces }) => {
document.getElementById('search-results').innerHTML = artPieces.join('');
}
现在我们可以使用 typescript 进行开发和构建。
文章来源:https://dev.to/steffanboodhoo/practical-webpack-basics-pure-javascript-frontend-356