配置 github 仓库:https://github.com/nystudio107/annotated-webpack-4-config
随着Web开发变得越来越复杂,我们需要工具来帮助我们构建现代网站。
这是一个复杂的webpack 4配置的完整的现实生产示例。
构建现代网站已成为自定义应用程序的开发。网站不仅仅被当作一个营销网站,因为它们还具备了传统应用的功能。
当一个过程变得复杂时,我们就会将其分解为可管理的组件,并使用工具自动化构建处理。这种情况不论我们是在制造汽车、起草法律文件还是建立网站。
使用正确的工具
像 webpack 这样的工具一直处于目前Web开发的最前沿,正是出于这个原因:它们都能帮助我们构建复杂的东西。
webpack 4 拥有一些令人兴奋的改进,对我来说最吸引人的是它在构建时速度的提升。所以我决定采用它。
准备好瓜子啤酒,因为这真的篇幅很长。
一年多前,我发表了一篇文章 A Gulp Workflow for Frontend Development Automation (前端自动化—— Gulp 工作流程),它展示了如何使用Gulp来完成同样的事情。然而在这段时间里,我已经越来越多地使用 VueJS
和 GraphQL
这样的前端框架,正如在 VueJS + GraphQL to make Practical Magic 这篇文章所讨论的那样。
我发现 webpack 使我更容易构建我现在正在制作的网站和应用程序类型,并且它还允许我使用最潮的工具链。
这还有一些其他的选择:
Laravel Mix 是 webpack 的封装构建工具层。它的简洁性很吸引人:您可以快速启动并运行,并且它可以完成 90% 您想要的操作。但剩下的10%意味着无论如何都需要 webpack 的介入。而且目前它还没有支持 webpack 4。
如果你只构建 VueJS ,那么 vue-cli 是非常好的选择。它也是 webpack 的封装构建工具层,大多数工作都可以胜任,并且做的很棒。但同样,当有些需求不能满足时,您还是需要了解 webpack。而且我并不总是专门使用 VueJS。
Neutrino 是一个有意思的封装构建工具层。我们是在 Neutrino: How I Learned to Stop Worrying and Love Webpack 这篇博客发现的。论述还是挺有想法的,通过将预制的组件拼凑在一起来构建 webpack 配置。但是学习它如何配置似乎与学习webpack 本身一样多。
如果您选择上述任何工具(甚至是其他工具),我都不会发表任何意见,但请注意,所有这些工具都有一个共同的特性:它们都是基于 webpack 的。
了解开发系统中底层如何工作终究会带来好处
最终,您只需要决定您希望自己在前端技术金字塔中处于什么样的位置。
有时候,我觉得了解像 webpack 这样的重要工具是如何工作的是有必要的。不久之前,我向 Sean Larkin(webpack核心团队成员之一)抱怨说webpack就像一个“黑匣子”。他的回答很简洁,但非常尖锐:
如果你没有打开它,它就是黑的。
他说的对。是时候打开盒子了。
本文不会教你众所周知的 webpack 的基础概念或者如何安装它的知识。这有足够的资源可供选择 —— 选择您最喜欢的学习方式:
…还有很多很多。相反,本文将解析一个相当复杂的 webpack 4 设置的完整工作示例。你可以全部使用它;你可以使用它的一部分。但希望你能从中有所收获。
在我继续学习 webpack 的过程中,我发现了很多教程视频,一堆文章讲述如何安装它和一些基本配置,但不是很多实际的 webpack 配置的完整示例。那么,请看这里。
当我开始通过打开盒子来学习 webpack 时,我有一个技术依赖的列表,我想参与到整个构建的过程中来。我也花时间学习了周边相关的技术,看看还有什么我可以用得着的。
正如 A Pretty Website Isn’t Enough 这篇文章里讨论过的那样,网站性能一直是我的主要关注点,所以在这个 webpack 配置中关注它也就不足为奇了。
所以这是我希望 webpack 为我做的事情,以及我希望在构建过程中加入的技术:
dev
和 prod
配置和构建。mozjpeg
,optipng
,svgo
等自动化工具对其进行优化是有意义的。.webp
,这种格式比JPEG更有效。.vue
组件作为我开发过程的一部分。擦,好多东西要搞啊,任重而道远啊。
其实还有更多,比如 Javascript 的自动压缩混淆,CSS 的压缩,还有其他一些我们希望从前端构建流程中规范标准化的东西。
我还希望它可以适应团队合作开发,开发团队可以为他们的本地开发环境使用不同的工具,并使配置易于维护和项目之间的复用。
可维护性和可复用性的重要性不容低估
您的前端框架/技术栈可能与我的不同,但应用的原则将是相同的。所以请继续阅读,无论你用什么!
为了向您概述设置的外观,这里有一个简单的项目树:
├── example.env
├── package.json
├── postcss.config.js
├── src
│ ├── css
│ │ ├── app.pcss
│ │ ├── components
│ │ │ ├── global.pcss
│ │ │ ├── typography.pcss
│ │ │ └── webfonts.pcss
│ │ ├── pages
│ │ │ └── homepage.pcss
│ │ └── vendor.pcss
│ ├── fonts
│ ├── img
│ │ └── favicon-src.png
│ ├── js
│ │ ├── app.js
│ │ └── workbox-catch-handler.js
│ └── vue
│ └── Confetti.vue
├── tailwind.config.js
├── templates
├── webpack.common.js
├── webpack.dev.js
├── webpack.prod.js
├── webpack.settings.js
└── yarn.lock
有关此处所示内容的完整源代码,请查看 annotated-webpack-4-config github 仓库。
所以核心的配置文件有:
.env
- webpack-dev-server 的环境特定设置;不需要提交到 gitwebpack.settings.js
- 一个 JSON-ish 设置文件,我们需要在项目改变时编辑的唯一文件webpack.common.js
- 两种类型构建的公用设置webpack.dev.js
- 本地开发构建的设置webpack.prod.js
- 生产环境构建的设置这里是一个如果把它们组合起来的图解:
目标是您只需要编辑项目之间的金色圆角矩形( .env
和 webpack.settings.js
)。
以这种方式分离出来使得配置文件的使用变得更加容易。即使您最终更改了我在此处提供的各种 webpack 配置文件,保持这种方法将有助于您长期维护它们。
别担心,我们稍后会详细介绍每个文件。
我们先从 package.json
下手讲起:
{
"name": "example-project",
"version": "1.0.0",
"description": "Example Project brand website",
"keywords": [
"Example",
"Keywords"
],
"homepage": "https://github.com/example-developer/example-project",
"bugs": {
"email": "[email protected]",
"url": "https://github.com/example-developer/example-project/issues"
},
"license": "SEE LICENSE IN LICENSE.md",
"author": {
"name": "Example Developer",
"email": "[email protected]",
"url": "https://example-developer.com"
},
"browser": "/web/index.php",
"repository": {
"type": "git",
"url": "git+https://github.com/example-developer/example-project.git"
},
"private": true,
这里没什么特别的东西,只是我们网站的信息,如 package.json 规范中所述。
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js --progress --hide-modules"
},
这些脚本代表了我们为项目提供的两个主要构建步骤:
要运行它们,我们只需使用开发环境中的 CLI 执行 yarn dev
或 yarn build
(如果我们使用 yarn
),如果我们使用 npm
,则运行 npm run dev
或 npm run build
运行构建。这些是您仅需要使用的两个命令。
请注意,通过 --config
标志,我们还传入单独的配置文件。这使我们可以将我们的 webpack 配置分为单独的逻辑文件,因为与生产版本相比,我们将为开发构建做很多不同的事情。
接下来我们介绍 browserslist:
"browserslist": {
"production": [
"> 1%",
"last 2 versions",
"Firefox ESR"
],
"legacyBrowsers": [
"> 1%",
"last 2 versions",
"Firefox ESR"
],
"modernBrowsers": [
"last 2 Chrome versions",
"not Chrome < 60",
"last 2 Safari versions",
"not Safari < 10.1",
"last 2 iOS versions",
"not iOS < 10.3",
"last 2 Firefox versions",
"not Firefox < 54",
"last 2 Edge versions",
"not Edge < 15"
]
},
这是一个基于人性化可读配置的特定浏览器列表。PostCSS autoprefixer 默认使用我们的 production
设置。我们将 legacyBrowsers
和 modernBrowsers
传递给Babel 来处理构建旧版本和新版本的 JavaScript 包。稍后会详细介绍!
接下来我们有介绍 devDependencies,它们是构建系统所需的所有 npm 包:
"devDependencies": {
"@babel/core": "^7.1.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.1.0",
"@babel/preset-env": "^7.1.0",
"@babel/register": "^7.0.0",
"@babel/runtime": "^7.0.0",
"autoprefixer": "^9.1.5",
"babel-loader": "^8.0.2",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.5.2",
"create-symlink-webpack-plugin": "^1.0.0",
"critical": "^1.3.4",
"critical-css-webpack-plugin": "^0.2.0",
"css-loader": "^1.0.0",
"cssnano": "^4.1.0",
"dotenv": "^6.1.0",
"file-loader": "^2.0.0",
"git-rev-sync": "^1.12.0",
"glob-all": "^3.1.0",
"html-webpack-plugin": "^3.2.0",
"ignore-loader": "^0.1.2",
"imagemin": "^6.0.0",
"imagemin-gifsicle": "^5.2.0",
"imagemin-mozjpeg": "^7.0.0",
"imagemin-optipng": "^5.2.1",
"imagemin-svgo": "^7.0.0",
"imagemin-webp": "^4.1.0",
"imagemin-webp-webpack-plugin": "^1.0.2",
"img-loader": "^3.0.1",
"mini-css-extract-plugin": "^0.4.3",
"moment": "^2.22.2",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss": "^7.0.2",
"postcss-extend": "^1.0.5",
"postcss-hexrgba": "^1.0.1",
"postcss-import": "^12.0.0",
"postcss-loader": "^3.0.0",
"postcss-nested": "^4.1.0",
"postcss-nested-ancestors": "^2.0.0",
"postcss-simple-vars": "^5.0.1",
"purgecss-webpack-plugin": "^1.3.0",
"purgecss-whitelister": "^2.2.0",
"resolve-url-loader": "^3.0.0",
"sane": "^4.0.1",
"save-remote-file-webpack-plugin": "^1.0.0",
"style-loader": "^0.23.0",
"symlink-webpack-plugin": "^0.0.4",
"terser-webpack-plugin": "^1.1.0",
"vue-loader": "^15.4.2",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.5.17",
"webapp-webpack-plugin": "https://github.com/brunocodutra/webapp-webpack-plugin.git",
"webpack": "^4.19.1",
"webpack-bundle-analyzer": "^3.0.2",
"webpack-cli": "^3.1.1",
"webpack-dashboard": "^2.0.0",
"webpack-dev-server": "^3.1.9",
"webpack-manifest-plugin": "^2.0.4",
"webpack-merge": "^4.1.4",
"webpack-notifier": "^1.6.0",
"workbox-webpack-plugin": "^3.6.2"
},
是不是看到相当多的包。是的,我们的构建过程确实有很多依赖。最后,这是我们在网站的前端需要使用的依赖项:
"dependencies": {
"@babel/polyfill": "^7.0.0",
"axios": "^0.18.0",
"tailwindcss": "^0.6.6",
"vue": "^2.5.17",
"vue-confetti": "^0.4.2"
}
}
显然,对于一个真实的网站/应用程序,依赖项中会有更多的包;但我们比较关心构建过程。
在 A Better package.json for the Frontend article 这篇文章,我也讨论过用类似的方式。这是为了隔离从项目到项目配置的变化只需单独的做 webpack.settings.js
的更改,并保留 webpack 配置本身不发生变化。
关键点是从一个项目到另一个项目,我们需要编辑的唯一文件是webpack.settings.js
由于大多数项目都有一些相似的构建步骤,我们可以创建一个适用于各种项目的 webpack 配置。我们只需要更改它运行的参数。
因此,我们的 webpack.settings.js
文件(项目变化时的参数)是和我们的 webpack 配置中的内容(如何操作数据以产生最终结果)之间的焦点分离。
// webpack.settings.js - webpack settings config
// node modules
require('dotenv').config();
// Webpack settings exports
// noinspection WebpackConfigHighlighting
module.exports = {
name: "Example Project",
copyright: "Example Company, Inc.",
paths: {
src: {
base: "./src/",
css: "./src/css/",
js: "./src/js/"
},
dist: {
base: "./web/dist/",
clean: [
"./img",
"./criticalcss",
"./css",
"./js"
]
},
templates: "./templates/"
},
urls: {
live: "https://example.com/",
local: "http://example.test/",
critical: "http://example.test/",
publicPath: "/dist/"
},
vars: {
cssName: "styles"
},
entries: {
"app": "app.js"
},
copyWebpackConfig: [
{
from: "./src/js/workbox-catch-handler.js",
to: "js/[name].[ext]"
}
],
criticalCssConfig: {
base: "./web/dist/criticalcss/",
suffix: "_critical.min.css",
criticalHeight: 1200,
criticalWidth: 1200,
ampPrefix: "amp_",
ampCriticalHeight: 19200,
ampCriticalWidth: 600,
pages: [
{
url: "",
template: "index"
}
]
},
devServerConfig: {
public: () => process.env.DEVSERVER_PUBLIC || "http://localhost:8080",
host: () => process.env.DEVSERVER_HOST || "localhost",
poll: () => process.env.DEVSERVER_POLL || false,
port: () => process.env.DEVSERVER_PORT || 8080,
https: () => process.env.DEVSERVER_HTTPS || false,
},
manifestConfig: {
basePath: ""
},
purgeCssConfig: {
paths: [
"./templates/**/*.{twig,html}",
"./src/vue/**/*.{vue,html}"
],
whitelist: [
"./src/css/components/**/*.{css,pcss}"
],
whitelistPatterns: [],
extensions: [
"html",
"js",
"twig",
"vue"
]
},
saveRemoteFileConfig: [
{
url: "https://www.google-analytics.com/analytics.js",
filepath: "js/analytics.js"
}
],
createSymlinkConfig: [
{
origin: "img/favicons/favicon.ico",
symlink: "../favicon.ico"
}
],
webappConfig: {
logo: "./src/img/favicon-src.png",
prefix: "img/favicons/"
},
workboxConfig: {
swDest: "../sw.js",
precacheManifestFilename: "js/precache-manifest.[manifestHash].js",
importScripts: [
"/dist/workbox-catch-handler.js"
],
exclude: [
/\.(png|jpe?g|gif|svg|webp)$/i,
/\.map$/,
/^manifest.*\\.js(?:on)?$/,
],
globDirectory: "./web/",
globPatterns: [
"offline.html",
"offline.svg"
],
offlineGoogleAnalytics: true,
runtimeCaching: [
{
urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
handler: "cacheFirst",
options: {
cacheName: "images",
expiration: {
maxEntries: 20
}
}
}
]
}
};
我们将在 webpack 配置部分介绍所有这些内容。这里要注意的重要一点是,我们把项目到项目会发生变化的一些数据从我们的 webpack 配置中分解出来,并分解到单独的 webpack.settings.js
文件中。
这意味着我们可以在 webpack.settings.js
文件中定义每个项目的不同之处,而不必与 webpack 配置本身扯皮。
即使 webpack.settings.js
文件只是 JavaScript,我也尽量将它保持为 JSON-ish,所以我们只是更改其中的简单设置。为了灵活性,我没有使用 JSON 作为文件格式,因此也允许添加注释。
为了可读性,我决定断个篇。后续内容请看:
实战 wepack 4 配置解析二