实战 webpack 4 配置解析一

配置 github 仓库:https://github.com/nystudio107/annotated-webpack-4-config

随着Web开发变得越来越复杂,我们需要工具来帮助我们构建现代网站。
这是一个复杂的webpack 4配置的完整的现实生产示例。
在这里插入图片描述
构建现代网站已成为自定义应用程序的开发。网站不仅仅被当作一个营销网站,因为它们还具备了传统应用的功能。

当一个过程变得复杂时,我们就会将其分解为可管理的组件,并使用工具自动化构建处理。这种情况不论我们是在制造汽车、起草法律文件还是建立网站。

使用正确的工具

像 webpack 这样的工具一直处于目前Web开发的最前沿,正是出于这个原因:它们都能帮助我们构建复杂的东西。

webpack 4 拥有一些令人兴奋的改进,对我来说最吸引人的是它在构建时速度的提升。所以我决定采用它。

准备好瓜子啤酒,因为这真的篇幅很长。

采用 webpack

一年多前,我发表了一篇文章 A Gulp Workflow for Frontend Development Automation (前端自动化—— Gulp 工作流程),它展示了如何使用Gulp来完成同样的事情。然而在这段时间里,我已经越来越多地使用 VueJSGraphQL 这样的前端框架,正如在 VueJS + GraphQL to make Practical Magic 这篇文章所讨论的那样。

我发现 webpack 使我更容易构建我现在正在制作的网站和应用程序类型,并且它还允许我使用最潮的工具链。

这还有一些其他的选择:

  • Laravel Mix 是 webpack 的封装构建工具层。它的简洁性很吸引人:您可以快速启动并运行,并且它可以完成 90% 您想要的操作。但剩下的10%意味着无论如何都需要 webpack 的介入。而且目前它还没有支持 webpack 4。

  • 如果你只构建 VueJS ,那么 vue-cli 是非常好的选择。它也是 webpack 的封装构建工具层,大多数工作都可以胜任,并且做的很棒。但同样,当有些需求不能满足时,您还是需要了解 webpack。而且我并不总是专门使用 VueJS。

  • Neutrino 是一个有意思的封装构建工具层。我们是在 Neu­tri­no: How I Learned to Stop Wor­ry­ing and Love Web­pack 这篇博客发现的。论述还是挺有想法的,通过将预制的组件拼凑在一起来构建 webpack 配置。但是学习它如何配置似乎与学习webpack 本身一样多。

如果您选择上述任何工具(甚至是其他工具),我都不会发表任何意见,但请注意,所有这些工具都有一个共同的特性:它们都是基于 webpack 的。

了解开发系统中底层如何工作终究会带来好处

最终,您只需要决定您希望自己在前端技术金字塔中处于什么样的位置。

有时候,我觉得了解像 webpack 这样的重要工具是如何工作的是有必要的。不久之前,我向 Sean Larkin(webpack核心团队成员之一)抱怨说webpack就像一个“黑匣子”。他的回答很简洁,但非常尖锐:

如果你没有打开它,它就是黑的。

他说的对。是时候打开盒子了。

本文不会教你众所周知的 webpack 的基础概念或者如何安装它的知识。这有足够的资源可供选择 —— 选择您最喜欢的学习方式:

…还有很多很多。相反,本文将解析一个相当复杂的 webpack 4 设置的完整工作示例。你可以全部使用它;你可以使用它的一部分。但希望你能从中有所收获。

在我继续学习 webpack 的过程中,我发现了很多教程视频,一堆文章讲述如何安装它和一些基本配置,但不是很多实际的 webpack 配置的完整示例。那么,请看这里。

我们从盒子里拿出了什么

当我开始通过打开盒子来学习 webpack 时,我有一个技术依赖的列表,我想参与到整个构建的过程中来。我也花时间学习了周边相关的技术,看看还有什么我可以用得着的。

正如 A Pret­ty Web­site Isn’t Enough 这篇文章里讨论过的那样,网站性能一直是我的主要关注点,所以在这个 webpack 配置中关注它也就不足为奇了。

所以这是我希望 webpack 为我做的事情,以及我希望在构建过程中加入的技术:

  • Development/Production(开发/生产) - 在本地开发中,我希望通过内存webpack-dev 服务器进行快速构建,并且对于生产环境构建(通常通过 buddy.works在Docker容器中完成),我希望进行所有可能的优化。因此,我们有单独的 devprod 配置和构建。
  • Hot Module Replacement (热模块替换) - 当我对我的 JavaScript,CSS或模板进行更改时,我希望网页能够无缝刷新。这极大地方便了开发:只需对刷新按钮说“不”。
  • Dynamic Code Splitting(动态代码拆分) - 我不想手动在配置文件中定义JavaScript块,我希望 webpack 为我整理。
  • Lazy Loading(懒加载) - 也称为异步动态模块加载。仅仅在需要时加载所需的代码/资源,而不阻止渲染。
  • Modern&Legacy JS Bundles - 我希望将现代 ES2015 + JavaScript 模块部署到支持它的75%以上的全球浏览器中,同时优雅地为旧版浏览器提供后备旧版软件包(包含所有已编译的代码和polyfill)。
  • Cache Bust­ing via manifest.json(通过 manifest.json 缓存 Busting) - 这允许我们为静态资产设置一个很久到期的数据,同时还确保它们在更改时自动更新缓存数据。
  • Crit­i­cal CSS  - 这使得首页加载速度明显加快。
  • Workbox Service Worker - 我们可以利用 Google 的 Workbox 项目为我们生成一个 Service Worker,以了解我们项目的所有资产。PWA,我们来了!
  • PostC­SS - 我认为它是“CSS的Babel”,像 SASS 和 SCSS 这样的东西都建立在它上面,它允许你现在就使用一些 CSS 未来的特性功能。
  • Image Opti­miza­tion(图片优化) - 到目前为止,图像是大多数网页上最大的内容,因此通过 mozjpegoptipngsvgo 等自动化工具对其进行优化是有意义的。
  • Auto­mat­ic .webp Cre­ation - Chrome,Edge 和 Firefox 都支持 .webp,这种格式比JPEG更有效。
  • VueJS - VueJS是我选择的前端框架。我希望能够使用单个文件 .vue 组件作为我开发过程的一部分。
  • Tailwind CSS - Tailwind 是一个实用工具集CSS框架,我用它在本地开发中进行快速原型设计,然后在生产坏境使用 PurgeCSS,以显著的减小大小。

擦,好多东西要搞啊,任重而道远啊。

其实还有更多,比如 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 的环境特定设置;不需要提交到 git
  • webpack.settings.js - 一个 JSON-ish 设置文件,我们需要在项目改变时编辑的唯一文件
  • webpack.common.js - 两种类型构建的公用设置
  • webpack.dev.js - 本地开发构建的设置
  • webpack.prod.js - 生产环境构建的设置

这里是一个如果把它们组合起来的图解:

在这里插入图片描述

目标是您只需要编辑项目之间的金色圆角矩形( .envwebpack.settings.js)。

以这种方式分离出来使得配置文件的使用变得更加容易。即使您最终更改了我在此处提供的各种 webpack 配置文件,保持这种方法将有助于您长期维护它们。

别担心,我们稍后会详细介绍每个文件。

解析 package.json

我们先从 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": "someone@example-developer.com",
        "url": "https://github.com/example-developer/example-project/issues"
    },
    "license": "SEE LICENSE IN LICENSE.md",
    "author": {
        "name": "Example Developer",
        "email": "someone@example-developer.com",
        "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"
    },

这些脚本代表了我们为项目提供的两个主要构建步骤:

  • dev - 在我们处理项目时使用它,它会启动 webpack-dev-server 以允许热模块替换(HMR),内存编译和其他细节。
  • build - 在我们进行生产部署时使用,它可以完成所有花哨和耗时的事情,例如需要为生产部署完成的 Critical CSS,JavaScript的压缩混淆等。

要运行它们,我们只需使用开发环境中的 CLI 执行 yarn devyarn build (如果我们使用 yarn),如果我们使用 npm,则运行 npm run devnpm 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 设置。我们将 legacyBrowsersmodernBrowsers 传递给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"
    }
}

显然,对于一个真实的网站/应用程序,依赖项中会有更多的包;但我们比较关心构建过程。

解析 WEBPACK.SETTINGS.JS

A Bet­ter package.json for the Fron­tend arti­cle 这篇文章,我也讨论过用类似的方式。这是为了隔离从项目到项目配置的变化只需单独的做 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 配置解析二

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值