博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用于前端开发的webpack4配置[带注释]
阅读量:6623 次
发布时间:2019-06-25

本文共 54271 字,大约阅读时间需要 180 分钟。

❤️觉得不错点个赞哟❤️。

随着web开发变得越来越复杂,我们需要使用工具来帮助我们构建现代化网站。这是一个完整通过复杂webpack4配置的线上生产例子。

构建现代化网站已经成为自定义应用程序开发,网站期望能做的更多,具有传统应用的功能,而不仅仅是一个推广网站。

随着一个流程变得复杂时,我们就会将它分解为可管理的组件,并使用工具进行自动化构建,比如制造汽车、起草法案[法律文件]、建立网站。

使用正确的工具完成工作

像webpack这样的工具一直处于现代web开发的最前沿,正是因为如此,它帮助我们构建复杂的事物。

webpack4拥有一些意想不到的改进,最吸引我的就是它在构建速度上面变得有多快,所以我决定采用它。

hold住,因为这是一篇充满大量信息的长篇文章。

采用webpack

一年多以前,我发表了一篇文章: [用于前端自动化的gulp工作流],讲解了如何使用gulp完成同样的事情。然而在这段时间里,我越来越多地使用像和这样的前端框架,如这篇文章。

我发现webpack让我更容易的去构建各种类型的网站以及应用程序,而且它也允许我使用最现代化的工具链。

还有其他选择:

  • 是基于webpack的构建工具层,它十分简洁,你可以快速启动并运行,它可以在90%的时间内完成你想要的任务,但剩下的10%无论如何都会进入到webpack,目前还不支持webpack4。

  • 如果你只是用VueJS前端框架,那么使用是个不错的选择,它也是基于webpack,大部分时间都可以工作,并且为你做一些意想不到的事情。但同样的,当它提供的功能已经满足不了你的需求,你还是需要用到webpack,而且我并不是只使用VueJS。

  • Neutrino也是基于webpack,我们可以关注博客:。神奇的点就是它可以通过像搭乐高积木一样去配置webpack,但学习使用让的成本跟学习webpack其实差不了多少。

如果你选择上述工具(或者其他工具),我不会对你提出任:它们都是基于webpack封装。

理解开发系统中层是如何工作的是有好处的。

最终,你只需要决定你希望站在前端技术金字塔中的哪个位置。

某些时候,我认为了解像webpack这样重要的工具是如何工作是有意义的。不久前,我向(webpack核心团队成员之一)抱怨说webpack就像一个“黑匣子”,他的回答简洁却非常精辟:

It’s only black if you haven’t opened it.[如果你没有打开这个所谓的“黑匣子”,它永远都是未知的。]

他说的对,是时候打开“黑匣子”了。

本文不会教你所有关于webpack的知识,甚至是如何安装它,下面有很多、资料给你选择,你可以选择你认为不错的方式:

  • ——简要概述了webpack的工作原理。

  • —— 建议读webpack官方文档,如果你想学好它的话

  • —— webpack教学视频

这样的资料还有很多,相反地本文将用webpack4配置一个复杂的完整工作例子,并添加注释。你可以使用完整的示例,也可以使用它的部分配置项,但希望你可以从中学到一些东西。在我学习webpack的过程中,我发现有很多教程视频,一堆文章给你将如何安装它并添加一些基础配置,但却大部分没有实际线上生产环境的webpack配置示例,所以我写了这篇文章。

WHAT WE GET OUT OF THE BOX

当我开始通过打开“黑匣子”来学习webpack时,我有一份我依赖的技术列表,我想将它成为构建流程的一部分。我也会花时间四处看看,看看在这个过程中,我还能采用什么。

正如在文章 讨论的那样,网站性能一直都是我关注的重点,所以在配置webpack过程中关注性能问题也很正常。

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

  • —— 在本地开发中,我通过进行快速构建,对于生产环境的构建(通常通过在Docker容器中构建),我希望尽可能优化每一个点。因此,我们区分devprod的配置以及构建。

  • —— 当我修改了js、css或者页面,我希望网页能够自动刷新,大幅度提高了开发效率:不需要你去点浏览器刷新按钮。

  • —— 我不想手动在配置文件中定义js chunk,所以我让webpack帮我解决这个问题。

  • —— 又称异步动态模块加载,在需要时加载所需的代码资源。

  • —— 我想将es2015 + JavaScript模块发布到能够支持全球75%以上的浏览器上,同时为低版本的浏览器提供一个补丁包(包括所有转码和polyfills)。

  • —— 可以让我们为静态资源设置缓存,同时保证它们在更改使自动重新缓存。

  • —— 根据文章,可以提高首页面的加载速度。

  • —— 我们可以使用Google的Workbox项目为我们创建一个 ,了解我们项目的所有东西[这句翻译的有点问题,可以看原文理解]。PWA,我们来了!

  • —— 我认为它是“css的babel”,像sass和scss都是基于它来构建,它让你可以使用即将推出的css功能。

  • —— 目前,图片仍然是大部分网页呈现的主要内容,所以可以通过mozjpegoptipngsvgo等自动化工具来压缩优化图片资源是很有必要的。

  • —— Chrome、Edge和FireFox都支持.webp文件,它比jpeg体积更小,节省资源。

  • —— VueJs是我这次用的前端框架,我希望能够通过单个文件.vue组件作为开发过程的一部分。

  • —— Tailwind是一个实用程序优先的css,我用它在本地开发中快速进行原型设计,然后通过PurgeCss进行生产,从而减小体积。

哇,看起来相当丰富的清单!

还有很多东西,比如JavaScript自动化、css压缩以及其他标准配置,去构建我们期望的前端系统。

我还希望它可以给开发团队使用,开发团队可以使用不同的工具应用在他们的本地开发环境,并使配置易于维护以及可以被其他项目重用。

The importance of maintainability and reusability can’t be understated [可维护性和复用性是非常重要的。]

你使用的前端框架或者技术栈可以跟我的不一样,但应用的规则其实是相同的,所以请继续阅读其余部分,不管你用的是什么技术栈!

PROJECT TREE & ORGANIZATION

为了让你了解程序的整体架构,这里展示一个简单的项目树:

├── 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复制代码

完整的代码可以查看:

在核心配置文件方法,包括:

  • .env—— webpack-dev-server特定于开发环境的设置,不需要在git中检查

  • webpack.settings.js —— 一个JSON-ish设置文件,我们需要在项目之间编辑的唯一文件

  • webpack.common.js —— 相同类型的构建放在统一设置文件

  • webpack.dev.js —— 设置本地开发各个构建

  • webpack.prod.js —— 设置生产环境各个构建

这是一个如何将以上配置组合成的图表:

目标是你只需要编辑项目之间的金色圆角区域(.env&webpack.settings.js)。

以这种形式分离出来使得配置文件使用变得更加容易,即使你最终修改了我原先提供的各种webpack配置文件,但保持这种方式有助于你长期去对配置文件进行维护。

别着急,我们等下会详细介绍每个文件。

ANNOTATED 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压缩等。

我们只需要在命令行执行以下操作: 如果我们使用的是yarn,输入yarn dev或者yarn build;如果使用的是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设置中,我们将legacyBrowsersmodernBrowsers传递给Babel用来处理传统[过去]和现代js包的构建[处理转码问题,兼容es6等写法],后面会有详细介绍。

接着是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"    },复制代码

没错,这里面依赖了很多npm包,但我们的构建过程确实做的事情需要用到它们。

最后,dependencies的使用:

"dependencies": {        "@babel/polyfill": "^7.0.0",        "axios": "^0.18.0",        "tailwindcss": "^0.6.6",        "vue": "^2.5.17",        "vue-confetti": "^0.4.2"    }}复制代码

显然,对于一个真实存在的网站或者应用,dependencies中会有更多npm包,但我们现在专注于构建过程。

ANNOTATED WEBPACK.SETTINGS.JS

我还使用了我在一文中讨论过的类似方法,为了封锁从项目之间配置变为单独的webpack.settings.js,并保持webpack配置本身不变。

The key concept is that the only file we need to edit from project to project is the webpack.settings.js. [关键概念是我们需要在项目之间编辑的唯一文件是webpack.settings.js]

由于大部分项目都有一些非常相似的事情需要完成,所以我们可以创建一个适用于各个项目的webpack配置,我们只需要更改它所操作的数据。

因此,在我们的webpack.settings.js配置文件中的内容(从项目到项目的数据)和webpack配置中的内容(如何操作这些数据产生最终结果)之间的关注点分离。

// webpack.settings.js - webpack settings config// node modulesrequire('dotenv').config();// Webpack settings exports// noinspection WebpackConfigHighlightingmodule.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文件是一个js文件,但我尽量将它保持为JSON-ish,所以我们只是更改其中的简单设置,我没有使用JSON作为文件格式的灵活性,也允许添加注释。

COMMON CONVENTIONS FOR WEBPACK CONFIGS

我为所有webpack配置文件(webpack.common.jswebpack.dev.jswebpack.prod.js)采用了一些约定,让它们看起来比较一致。

每个配置文件都有两个内置配置:

  • legacyConfig —— 适用于旧版ES5构建的配置

  • modernConfig —— 适用于构建现代ES2015+版本的配置

我们这样做是因为我们有单独的配置来创建兼容旧版本与现代构建,使它们在逻辑独立。webpack.common.js 也有一个baseConfig,为了保证组织的纯粹。

可以把它想象成面向对象编程,其中各种配置项目继承,baseConfig作为根对象。

为了保证配置简洁清晰和具有可读性,采用的另一个约定是为各种webpack插件和需要配置的其他webpack片段配置configure()函数,而不是全部混在一起。

这样做是因为在webpack.settings.js中的一些数据需要在使用webpack之前进行转换,并且由于过去/现代构建,我们需要根据构建类型返回不同的配置。

它还使配置文件更具可读性。

作为一个通用的webpack概念,要知道webpack本身只知道如何加载JavaScript和JSON。要加载其他东西,需要使用对应的,我们将在webpack配置中使用许多不同的加载器。

ANNOTATED WEBPACK.COMMON.JS

现在让我们看一下webpack.common.js配置文件,包含devprod构建类型间共享的所有配置。

// webpack.common.js - common webpack configconst LEGACY_CONFIG = 'legacy';const MODERN_CONFIG = 'modern';// node modulesconst path = require('path');const merge = require('webpack-merge');// webpack pluginsconst CopyWebpackPlugin = require('copy-webpack-plugin');const ManifestPlugin = require('webpack-manifest-plugin');const VueLoaderPlugin = require('vue-loader/lib/plugin');const WebpackNotifierPlugin = require('webpack-notifier');// config filesconst pkg = require('./package.json');const settings = require('./webpack.settings.js');复制代码

在一开始,我们引入了我们需要的node包,以及需要使用的webpack插件。然后我们导入webpack.settings.js 作为settings,以便我们可以访问那里的设置,并将package.json作为pkg导入,对其进行访问。

CONFIGURATION FUNCTIONS

这是configureBabelLoader()的设置:

// Configure Babel loaderconst configureBabelLoader = (browserList) => {   return {       test: /\.js$/,       exclude: /node_modules/,       use: {           loader: 'babel-loader',           options: {               presets: [                   [                       '@babel/preset-env', {                       modules: false,                       useBuiltIns: 'entry',                       targets: {                           browsers: browserList,                       },                   }                   ],               ],               plugins: [                   '@babel/plugin-syntax-dynamic-import',                   [                       "@babel/plugin-transform-runtime", {                       "regenerator": true                   }                   ]               ],           },       },   };};复制代码

函数configureBabelLoader()配置babel-loader来处理所有js后缀文件的加载,它使用而不是.babelrc文件,因此我们可以把所以内容保留在webpack配置文件中。

Babel可以将现代ES2015+(以及其他许多语言,如TypeScript或CoffeeScript)编译为针对特定浏览器或标准的JavaScript。我们将browserList作为参数传入,这样我们可以为旧版浏览器构建现代ES2015+模块和用polyfills兼容旧版ES5。

在我们的HTML中,我们只做这样的事情:

复制代码

不用polyfills,不用大惊小怪,旧版浏览器忽略type="module"脚本,并获取main-legacy.js,新版浏览器加载main.js,忽略nomodule,看起来很棒,真庆幸我想出了这个想法!为了不让你觉得这种方法是极端,。

插件甚至可以在web浏览器实现之前进行动态导入,这使我们可以。

那么到底在说啥?这意味着我们可以做这样的事:

// App mainconst main = async () => {   // Async load the vue module   const Vue = await import(/* webpackChunkName: "vue" */ 'vue');   // Create our vue instance   const vm = new Vue.default({       el: "#app",       components: {           'confetti': () => import(/* webpackChunkName: "confetti" */ '../vue/Confetti.vue'),       },   });};// Execute async functionmain().then( (value) => {});复制代码

有两点:

1、通过/* webpackChunkName: "vue" */,我们告诉webpack希望这个动态代码拆分块被命名。

2、由于我们在异步函数(“main”)中使用import(),该函数等待动态加载的JavaScript导入的结果,而其余的代码以其方式继续。

我们已经有效地告诉webpack,我们希望我们的块通过代码分割,而不是通过配置,通过@babel/plugin-syntax-dynamic-import的自带魔法,可以根据需要异步加载此JavaScript块。

注意,我们也是使用.vue单文件组件做了同样的操作,很好。

除了使用await,我们也可以在import()Promise返回后执行我们的代码:

// Async load the vue moduleimport(/* webpackChunkName: "vue" */ 'vue').then(Vue => {   // Vue has loaded, do something with it   // Create our vue instance   const vm = new Vue.default({       el: "#app",       components: {           'confetti': () => import(/* webpackChunkName: "confetti" */ '../vue/Confetti.vue'),       },   });});复制代码

这里我们使用了Promise,而不是await,因此我们知道动态导入已经成功并且可以愉快地使用Vue

如果你足够仔细,你可以看到我们通过Promises有效地解决了JavaScript依赖关系,太棒了!

我们甚至可以在用户点击了某些内容,滚动到某个位置或者满足其他条件后去加载某些JavaScript快等有趣的事情。

查看更多关于信息。

如果你有兴趣了解更多有关Babel的信息,可以查看这篇文章。

接下来我们有configureEntries()

// Configure Entriesconst configureEntries = () => {   let entries = {};   for (const [key, value] of Object.entries(settings.entries)) {       entries[key] = path.resolve(__dirname, settings.paths.src.js + value);   }   return entries;};复制代码

这里我们通过swttings.entrieswebpack.settings.js中拿到webpack ,对于(SPA),只存在一个entry。对于更传统的网站,你可能有几个entry(每页模版可能有一个entry)。

无论哪种方式,由于我们已经在webpack.settings.js中定义了entry points,所以很容易在文件对其进行配置,entry points实际上只是一个<script src =“app.js”> </ script>标记,你将在HTML中包含该标记以引入JavaScript。

由于我们使用的是动态导入模块,因此我们通常在页面上只有一个<script></script>标签;其余的JavaScript会根据需要动态加载。

接下来我们有configureFontLoader()函数:

// Configure Font loaderconst configureFontLoader = () => {   return {       test: /\.(ttf|eot|woff2?)$/i,       use: [           {               loader: 'file-loader',               options: {                   name: 'fonts/[name].[ext]'               }           }       ]   };};复制代码

devprod构建字体加载是相同的,所以我们把它写在这里,对于我们使用的任何本地字体,我们可以通知webpack在JavaScript中加载它们:

import comicsans from '../fonts/ComicSans.woff2';复制代码

接下来我们有configureManifest()函数:

// Configure Manifestconst configureManifest = (fileName) => {   return {       fileName: fileName,       basePath: settings.manifestConfig.basePath,       map: (file) => {           file.name = file.name.replace(/(\.[a-f0-9]{32})(\..*)$/, '$2');           return file;       },   };};复制代码

这会为基于文件名的缓存清除配置,简单来说,webpack知道我们需要的所有JavaScript、css和其他资源,所以它可以生成一个指向带哈希命名的资源清单,例如:

{ "vendors~confetti~vue.js": "/dist/js/vendors~confetti~vue.03b9213ce186db5518ea.js", "vendors~confetti~vue.js.map": "/dist/js/vendors~confetti~vue.03b9213ce186db5518ea.js.map", "app.js": "/dist/js/app.30334b5124fa6e221464.js", "app.js.map": "/dist/js/app.30334b5124fa6e221464.js.map", "confetti.js": "/dist/js/confetti.1152197f8c58a1b40b34.js", "confetti.js.map": "/dist/js/confetti.1152197f8c58a1b40b34.js.map", "js/precache-manifest.js": "/dist/js/precache-manifest.f774c437974257fc8026ca1bc693655c.js", "../sw.js": "/dist/../sw.js"}复制代码

我们传入文件名,因为创建一个现代的monifest.json以及一个用于兼容的manifest-legacy.json,它们分别具有现代ES2015+模块和兼容旧版ES5模块的入口点。对于为现代以及旧版本生成的资源,这两个json文件中的关键点都是一致的。

接下来我们有一个相当标准的configureVueLoader()配置:

// Configure Vue loaderconst configureVueLoader = () => {   return {       test: /\.vue$/,       loader: 'vue-loader'   };};复制代码

这配置只是让我们轻松解析,webpack负责为你提取适当的HTML、CSS和Javascript。

BASE CONFIG

baseConfig将与modernConfiglegacyConfig合并:

// The base webpack configconst baseConfig = {   name: pkg.name,   entry: configureEntries(),   output: {       path: path.resolve(__dirname, settings.paths.dist.base),       publicPath: settings.urls.publicPath   },   resolve: {       alias: {           'vue$': 'vue/dist/vue.esm.js'       }   },   module: {       rules: [           configureVueLoader(),       ],   },   plugins: [       new WebpackNotifierPlugin({title: 'Webpack', excludeWarnings: true, alwaysNotify: true}),       new VueLoaderPlugin(),   ]};复制代码

这里所有的配置都是非常标准的webpack配置,但请注意我们将vue$指向vue/dist/vue.esm.js,以便我们可以获得Vue的ES2015模块版本。

我们使用插件以直观的方式告诉我们构建的状态。

LEGACY CONFIG

legacyConfig配置用于使用合适的polyfill构建兼容旧版本ES5:

// Legacy webpack configconst legacyConfig = {   module: {       rules: [           configureBabelLoader(Object.values(pkg.browserslist.legacyBrowsers)),       ],   },   plugins: [       new CopyWebpackPlugin(           settings.copyWebpackConfig       ),       new ManifestPlugin(           configureManifest('manifest-legacy.json')       ),   ]};复制代码

请注意,我们将pkg.browserslist.legacyBrowsers传递给configureBabelLoader(),将manifest-legacy.json传递给configureManifest()

我们还在此配置中加入了CopyWebpackPlugin插件,我们只需要复制settings.copyWebpackConfig中定义的文件一次。

MODERN CONFIG

modernConfig用于构建现代ES2015 Javascript模块,不需要借助其他东西:

// Modern webpack configconst modernConfig = {   module: {       rules: [           configureBabelLoader(Object.values(pkg.browserslist.modernBrowsers)),       ],   },   plugins: [       new ManifestPlugin(           configureManifest('manifest.json')       ),   ]};复制代码

请注意,我们将pkg.browserslist.modernBrowsers传递给configureBabelLoader(),将manifest.json传递给configureManifest()

MODULE.EXPORTS

最后,module.exports使用webpack-merge插件将之前的配置合并在一起,并返回webpack.dev.jswebpack.prod.js使用的对象。

// Common module exports// noinspection WebpackConfigHighlightingmodule.exports = {   'legacyConfig': merge(       legacyConfig,       baseConfig,   ),   'modernConfig': merge(       modernConfig,       baseConfig,   ),};复制代码

ANNOTATED WEBPACK.DEV.JS

现在让我们看看webpack.dev.js配置文件,它包含了我们开发项目时用于构建的所有设置,与webpack.common.js文件中的设置合并,形成一个完整的webpack配置。

// webpack.dev.js - developmental buildsconst LEGACY_CONFIG = 'legacy';const MODERN_CONFIG = 'modern';// node modulesconst merge = require('webpack-merge');const path = require('path');const sane = require('sane');const webpack = require('webpack');// webpack pluginsconst Dashboard = require('webpack-dashboard');const DashboardPlugin = require('webpack-dashboard/plugin');const dashboard = new Dashboard();// config filesconst common = require('./webpack.common.js');const pkg = require('./package.json');const settings = require('./webpack.settings.js');复制代码

在序言中,我们再次引入了需要用到的node包,以及使用的webpack插件,然后引入webpack.settings.js作为settings,以便我们可以访问那里的设置,并导入package.json作为pkg,以便访问那里的一些设置。

我们同时还导入了webpack.common.js常用的webpack配置,并将合并到我们的开发设置。

CONFIGURATION FUNCTIONS

这是configureDevServer()的配置:

// Configure the webpack-dev-serverconst configureDevServer = (buildType) => {   return {       public: settings.devServerConfig.public(),       contentBase: path.resolve(__dirname, settings.paths.templates),       host: settings.devServerConfig.host(),       port: settings.devServerConfig.port(),       https: !!parseInt(settings.devServerConfig.https()),       quiet: true,       hot: true,       hotOnly: true,       overlay: true,       stats: 'errors-only',       watchOptions: {           poll: !!parseInt(settings.devServerConfig.poll()),           ignored: /node_modules/,       },       headers: {           'Access-Control-Allow-Origin': '*'       },       // Use sane to monitor all of the templates files and sub-directories       before: (app, server) => {           const watcher = sane(path.join(__dirname, settings.paths.templates), {               glob: ['**/*'],               poll: !!parseInt(settings.devServerConfig.poll()),           });           watcher.on('change', function(filePath, root, stat) {               console.log('  File modified:', filePath);               server.sockWrite(server.sockets, "content-changed");           });       },   };};复制代码

当我们进行生产构建时,webpack绑定所有各种资源并保存到文件系统中,相比之下,当我们在本地项目中开发时,我们通过使用开发构建:

  • 启动为我们的资源提供服务的本地 web服务器。

  • 为了提升速度,在内存而不是文件系统中构建我们的资源。

  • 重新构建资源,如JavaScript、css、Vue组件等等,通过使用,当我们修改了这些资源,可以不需要重新加载界面。

  • 在更改模版时将会重新加载页面。

这类似于更复杂的变体,大大加快了开发速度。

唯一不同的是我们这里使用了Sane监控不需要通过webpack运行的文件(本例中我们的模板),当该文件修改时,重新加载页面。

注意,webpack-dev-server的配置再次引用了webpack.settings.js文件,对于大部分人来说默认值可能没问题,但我使用作为本地开发,像我们在文章讨论的那样,意味着我在Homestead VM中运行所有的开发工具。

因此,webpack.settings.js可以从一个.env文件中读取拥有特定的devServer配置,而不是在我的weboack.settings.js文件中对本地开发环境进行硬编码(因为它可能因人而异):

// .env file DEVSERVER settings# webpack example settings for Homestead/VagrantDEVSERVER_PUBLIC="http://192.168.10.10:8080"DEVSERVER_HOST="0.0.0.0"DEVSERVER_POLL=1DEVSERVER_PORT=8080DEVSERVER_HTTPS=0复制代码

你可以使用不同的配置,因此可以根据需要在.env文件中更改设置,背后的想法是我们在.env文件中定义了一个特定于环境的配置,不会将其签入git repo。如果.env文件不存在,那很好,使用默认值:

// webpack.settings.js devServerConfig defaultsdevServerConfig: {    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,},复制代码

接下来是configureImageLoader()配置:

// webpack.dev.js configureImageLoader()// Configure Image loaderconst configureImageLoader = (buildType) => {    if (buildType === LEGACY_CONFIG) {        return {            test: /\.(png|jpe?g|gif|svg|webp)$/i,            use: [                {                    loader: 'file-loader',                    options: {                        name: 'img/[name].[hash].[ext]'                    }                }            ]        };    }    if (buildType === MODERN_CONFIG) {        return {            test: /\.(png|jpe?g|gif|svg|webp)$/i,            use: [                {                    loader: 'file-loader',                    options: {                        name: 'img/[name].[hash].[ext]'                    }                }            ]        };    }};复制代码

传入buildType参数,以便返回不同的结果,具体取决于它是旧版本还是新版构建,在该例子中,我们返回了相同的配置,但可以想象可能会改变。

值得注意的是,这只是适用于我们webpack构建中包含的图片;许多其他的图片来自于其他地方(CMS系统,资产管理系统等等)。

要让webpack知道这里有图像,需要将其导入到你的JavaScript文件中:

import Icon from './icon.png';复制代码

有关这方面的更多详细信息,请查看webpack文档“”部分。

接下来是configurePostcssLoader()配置:

// Configure the Postcss loaderconst configurePostcssLoader = (buildType) => {    // Don't generate CSS for the legacy config in development    if (buildType === LEGACY_CONFIG) {        return {            test: /\.(pcss|css)$/,            loader: 'ignore-loader'        };    }    if (buildType === MODERN_CONFIG) {        return {            test: /\.(pcss|css)$/,            use: [                {                    loader: 'style-loader',                },                {                    loader: 'vue-style-loader',                },                {                    loader: 'css-loader',                    options: {                        importLoaders: 2,                        sourceMap: true                    }                },                {                    loader: 'resolve-url-loader'                },                {                    loader: 'postcss-loader',                    options: {                        sourceMap: true                    }                }            ]        };    }};复制代码

我们使用来处理所有的css,包括。我觉得PostCSS是css的Babel,它将各种高级css功能编程成浏览器可以解析的普通css。

对于webpack加载器,它们的处理顺序与列出顺序相反:

  • —— 将文件加载并处理为PostCSS

  • —— 将css中的所有url()重写为相对路径

  • —— 解析我们所有的CSS @importurl()

  • —— 将.vue 单文件中注入所有的css。

  • —— 将所有CSS注入到中

我们在本地开发过程中不需要将所有css文件提取到最小的文件中,相反我们只是让style-loader在我们的文档中内联它。

webpack-dev-server为css使用热模块替换(HMR),每当我们修改样式时,它都会重新构建css并自动注入,很神奇(what)。

我们通过引入它来告知webpack去解析:

import styles from '../css/app.pcss';复制代码

在webpack文档的部分中有详细讨论。

我们从App.js入口点执行此操作,将此视为PostCSS的入口点,app.pcss文件@import我们项目中使用到的所有CSS,后面会对此进行详细介绍。

MODULE.EXPORTS

最后,module.exports使用webpack-merge包将webpack.common.js中的common.legacyConfig与我们的开发旧版兼容配置合并,并将common.modernConfig与开发环境现代配置合并:

// Development module exportsmodule.exports = [    merge(        common.legacyConfig,        {            output: {                filename: path.join('./js', '[name]-legacy.[hash].js'),                publicPath: settings.devServerConfig.public() + '/',            },            mode: 'development',            devtool: 'inline-source-map',            devServer: configureDevServer(LEGACY_CONFIG),            module: {                rules: [                    configurePostcssLoader(LEGACY_CONFIG),                    configureImageLoader(LEGACY_CONFIG),                ],            },            plugins: [                new webpack.HotModuleReplacementPlugin(),            ],        }    ),    merge(        common.modernConfig,        {            output: {                filename: path.join('./js', '[name].[hash].js'),                publicPath: settings.devServerConfig.public() + '/',            },            mode: 'development',            devtool: 'inline-source-map',            devServer: configureDevServer(MODERN_CONFIG),            module: {                rules: [                    configurePostcssLoader(MODERN_CONFIG),                    configureImageLoader(MODERN_CONFIG),                ],            },            plugins: [                new webpack.HotModuleReplacementPlugin(),                new DashboardPlugin(dashboard.setData),            ],        }    ),];复制代码

通过module.exports中返回一个数组,我们告知webpack有多个需要执行的编译:一个用于旧版兼容构建,另一个用于新版构建。

对于旧版构建,我们将处理后的JavaScript命名为[name]-legacy.[hash].js,而新版构建命名为[name].[hash].js

通过设置modedevelopment,告知webpack这是开发环境构建。

将设置为inline-source-map,我们要求将CSS/JavsScript的.map内联到文件中,虽然构建出来的项目会偏大,但是便于开发调试。

通过插件,可以支持Webpack的热模块替换(HMR)。

插件让我们觉得自己是一个宇航员,拥有一个酷炫的面板:

我发现DashboardPlugin插件开发HUD比默认的webpack进度展示更直观。

到这里,现在已经为我们项目提供了一个很好的开发环境配置,查看热模块替换视频,了解该操作的。

ANNOTATED WEBPACK.PROD.JS

现在我们看看webpack.prod.js配置文件,它包含我们正在处理项目时用于生产构建的所有配置。它与webpack.common.js中的设置合并,形成一个完整的webpack配置。

// webpack.prod.js - production buildsconst LEGACY_CONFIG = 'legacy';const MODERN_CONFIG = 'modern';// node modulesconst git = require('git-rev-sync');const glob = require('glob-all');const merge = require('webpack-merge');const moment = require('moment');const path = require('path');const webpack = require('webpack');// webpack pluginsconst BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;const CleanWebpackPlugin = require('clean-webpack-plugin');const CreateSymlinkPlugin = require('create-symlink-webpack-plugin');const CriticalCssPlugin = require('critical-css-webpack-plugin');const HtmlWebpackPlugin = require('html-webpack-plugin');const ImageminWebpWebpackPlugin = require('imagemin-webp-webpack-plugin');const MiniCssExtractPlugin = require('mini-css-extract-plugin');const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');const PurgecssPlugin = require('purgecss-webpack-plugin');const SaveRemoteFilePlugin = require('save-remote-file-webpack-plugin');const TerserPlugin = require('terser-webpack-plugin');const WebappWebpackPlugin = require('webapp-webpack-plugin');const WhitelisterPlugin = require('purgecss-whitelister');const WorkboxPlugin = require('workbox-webpack-plugin');// config filesconst common = require('./webpack.common.js');const pkg = require('./package.json');const settings = require('./webpack.settings.js');复制代码

我们再次引入了在序言中涉及到的node包,以及我们使用的webpack插件,然后将webpack.settings.js作为settings导入,并将package.json作为pkg导入,便于访问需要用到的配置。

我们还导入了webpack.common.js中公共的webpack配置,我们将与开发设置合并。

TAILWIND EXTRACTOR

该类是的自定义提取器,允许在类名中使用特殊字符。

// Custom PurgeCSS extractor for Tailwind that allows special characters in// class names.//// https://github.com/FullHuman/purgecss#extractorclass TailwindExtractor {    static extract(content) {        return content.match(/[A-Za-z0-9-_:\/]+/g) || [];    }}复制代码

这取自Tailwind CSS文档中这一部分。有关此提取器如何与 purgcss 配合使用的详细信息, 请参阅下文, 让你的css变得更加的整洁。

CONFIGURATION FUNCTIONS

这是configureBanner()函数:

// Configure file bannerconst configureBanner = () => {    return {        banner: [            '/*!',            ' * @project        ' + settings.name,            ' * @name           ' + '[filebase]',            ' * @author         ' + pkg.author.name,            ' * @build          ' + moment().format('llll') + ' ET',            ' * @release        ' + git.long() + ' [' + git.branch() + ']',            ' * @copyright      Copyright (c) ' + moment().format('YYYY') + ' ' + settings.copyright,            ' *',            ' */',            ''        ].join('\n'),        raw: true    };};复制代码

这只是为我们生成的每个文件添加了一个带有项目名称、文件名、作者和 git 信息的banner。

接着是configureBundleAnalyzer()

// webpack.prod.js configureBundleAnalyzer()// Configure Bundle Analyzerconst configureBundleAnalyzer = (buildType) => {    if (buildType === LEGACY_CONFIG) {        return {            analyzerMode: 'static',            reportFilename: 'report-legacy.html',        };    }    if (buildType === MODERN_CONFIG) {        return {            analyzerMode: 'static',            reportFilename: 'report-modern.html',        };    }};复制代码

使用 插件为我们的新版和旧版本构建生成一份报告,并且生成一个独立可交互的HTML页面,可以查看webpack打包后的确切内容。

我发现这个插件挺有用,可以帮助我缩小最终构建包的大小,而且确切地了解了webpack构建了什么,所以我已经把它作为项目生产构建过程的一部分。

接着是configureCriticalCss()

// webpack.prod.js configureCriticalCss()// Configure Critical CSSconst configureCriticalCss = () => {    return (settings.criticalCssConfig.pages.map((row) => {            const criticalSrc = settings.urls.critical + row.url;            const criticalDest = settings.criticalCssConfig.base + row.template + settings.criticalCssConfig.suffix;            let criticalWidth = settings.criticalCssConfig.criticalWidth;            let criticalHeight = settings.criticalCssConfig.criticalHeight;            // Handle Google AMP templates            if (row.template.indexOf(settings.criticalCssConfig.ampPrefix) !== -1) {                criticalWidth = settings.criticalCssConfig.ampCriticalWidth;                criticalHeight = settings.criticalCssConfig.ampCriticalHeight;            }            console.log("source: " + criticalSrc + " dest: " + criticalDest);            return new CriticalCssPlugin({                base: './',                src: criticalSrc,                dest: criticalDest,                extract: false,                inline: false,                minify: true,                width: criticalWidth,                height: criticalHeight,            })        })    );};复制代码

使用插件通过webpack.settings.js中的settings.criticalCssConfig.pages进行分块,为我们的网站生成CriticalCSS。

需要注意的是,如果传入的页面在任何位置的名字都包含settings.criticalCssConfig.ampPrefix,则它将通过传入非常大的高度为整个网页(而不仅仅是上面的折叠内容)生成CriticalCSS。

这里不会详细介绍CriticalCSS,有关它的更多资料,请查看这篇文章。

接着是configureCleanWebpack()

// Configure Clean webpackconst configureCleanWebpack = () => {    return {        root: path.resolve(__dirname, settings.paths.dist.base),        verbose: true,        dry: false    };};复制代码

这只是使用从我们的webpack.settings.js中删除 settings.paths.dist.base 中的生成目录。

接着是configureHtml()

// Configure Html webpackconst configureHtml = () => {    return {        templateContent: '',        filename: 'webapp.html',        inject: false,    };};复制代码

这将使用与(见下文)插件为我们的favicons生成HTML。注意,我们在templateContent中传入一个空字符串,以便输出只是WebappWebpackPlugin的原始输出。

接着是configureImageLoader()

// Configure Image loaderconst configureImageLoader = (buildType) => {    if (buildType === LEGACY_CONFIG) {        return {            test: /\.(png|jpe?g|gif|svg|webp)$/i,            use: [                {                    loader: 'file-loader',                    options: {                        name: 'img/[name].[hash].[ext]'                    }                }            ]        };    }    if (buildType === MODERN_CONFIG) {        return {            test: /\.(png|jpe?g|gif|svg|webp)$/i,            use: [                {                    loader: 'file-loader',                    options: {                        name: 'img/[name].[hash].[ext]'                    }                },                {                    loader: 'img-loader',                    options: {                        plugins: [                            require('imagemin-gifsicle')({                                interlaced: true,                            }),                            require('imagemin-mozjpeg')({                                progressive: true,                                arithmetic: false,                            }),                            require('imagemin-optipng')({                                optimizationLevel: 5,                            }),                            require('imagemin-svgo')({                                plugins: [                                    {convertPathData: false},                                ]                            }),                        ]                    }                }            ]        };    }};复制代码

我们传入buildType参数,以至于我们可以返回不同的结果,具体取决于它是新版还是旧版构建。我们通过优化处理图像,通过进行新版构建。

我们只对新版构建执行此操作,因为花费时间去处理优化新版本和旧版本的图像没有意义(图像对于两者都是一样的)。

需要注意的是,这只适用于我们的webpack构建中包含的图像,许多其他图像资源其实来自来与其他地方(cms 系统、资产管理系统等)。

要让webpack优化图像,请将其导入 JavaScript:

import Icon from './icon.png';复制代码

更多详细信息,请查看webpack文档对应部分。

接着是我们的configureOptimization()配置:

// Configure optimizationconst configureOptimization = (buildType) => {    if (buildType === LEGACY_CONFIG) {        return {            splitChunks: {                cacheGroups: {                    default: false,                    common: false,                    styles: {                        name: settings.vars.cssName,                        test: /\.(pcss|css|vue)$/,                        chunks: 'all',                        enforce: true                    }                }            },            minimizer: [                new TerserPlugin(                    configureTerser()                ),                new OptimizeCSSAssetsPlugin({                    cssProcessorOptions: {                        map: {                            inline: false,                            annotation: true,                        },                        safe: true,                        discardComments: true                    },                })            ]        };    }    if (buildType === MODERN_CONFIG) {        return {            minimizer: [                new TerserPlugin(                    configureTerser()                ),            ]        };    }};复制代码

这是的配置,对于旧版构建(执行此操作两次没有任何意义),我们使用插件将项目里使用到的css提取到单个css文件中。如果您以前使用过webpack,那么以前应该已经使用过ExtractTextPlugin来执行过此操作,然而现在不需要这么做了。

我们还使用了插件通过删除重复的规则来优化生成的css,并通过cssnano压缩css。

最后,我们将Javascript minimizer设置成,这是因为[UglifyJsPlugin] ()不再支持最小化ES2015+JavaScript。由于我们正在生成新版es2015+bundles,我们需要它。

接着是configurePostcssLoader()

// Configure Postcss loaderconst configurePostcssLoader = (buildType) => {    if (buildType === LEGACY_CONFIG) {        return {            test: /\.(pcss|css)$/,            use: [                MiniCssExtractPlugin.loader,                {                    loader: 'css-loader',                    options: {                        importLoaders: 2,                        sourceMap: true                    }                },                {                    loader: 'resolve-url-loader'                },                {                    loader: 'postcss-loader',                    options: {                        sourceMap: true                    }                }            ]        };    }    // Don't generate CSS for the modern config in production    if (buildType === MODERN_CONFIG) {        return {            test: /\.(pcss|css)$/,            loader: 'ignore-loader'        };    }};复制代码

这个配置看起来十分类似于开发版本的configurePostcssLoader(),除了最终加载器,我们使用将所有css提取到一个文件中。

我们只对旧版兼容构建执行此操作,因为对每个构建执行它没有意义(css是相同的)。我们使用进行新版构建,因此我们的.css和.pcss文件存在一个加载器,但什么都没做。

如前面说到,我们使用处理所有的css,包括,我认为它是CSS的babel,因为它将各种高级的css功能编译成你的浏览器可以解析的普通css。

同样,对于webpack加载器,它们按照列出的相反顺序进行处理:

  • —— 将文件加载并处理为 PostCSS

  • —— 将css中的所有url()重写为相对路径

  • —— 解析我们所有的CSS @import 和 url()

  • —— 将所有css提取到一个文件中

由于这是一个生产环境构建,我们使用MiniCssExtractPlugin.loader提取所有使用到的css,并保存到.css文件中。CSS也被最小化,并针对生产环境进行了优化。

我们通过引入css文件告知webpack:

import styles from '../css/app.pcss';复制代码

这在webpack文档的有详细介绍。

我们从App.js入口点执行此操作,将此视为postCSS的入口点,app.pcss文件@import我们项目使用的所有CSS,稍后将详细介绍。

接着是configurePurgeCss()

// Configure PurgeCSSconst configurePurgeCss = () => {    let paths = [];    // Configure whitelist paths    for (const [key, value] of Object.entries(settings.purgeCssConfig.paths)) {        paths.push(path.join(__dirname, value));    }    return {        paths: glob.sync(paths),        whitelist: WhitelisterPlugin(settings.purgeCssConfig.whitelist),        whitelistPatterns: settings.purgeCssConfig.whitelistPatterns,        extractors: [            {                extractor: TailwindExtractor,                extensions: settings.purgeCssConfig.extensions            }        ]    };};复制代码

是一个出色的实用程序优先的CSS框架,它允许快速原型化,因为在本地开发中,很少需要实际编写任何css。 相反,你只需要使用提供的实用程序CSS类。

缺点就是生成的CSS可能有点大,这时候就需要用到,它将解析所有HTML/template/Vue/任何文件,并删除没有使用到的CSS。

节省的空间可能很大,Tailwind CSS和PurgeCSS是天作之合。我们在博客中深入讨论了这个问题。

它遍历settings.purgeCssConfig.paths中的所有路径globs,以寻找要保留的CSS规则,任何未找到的CSS规则都会从我们生成的CSS构建中删除。

我们还使用了,当我们知道不希望某些CSS 被剥离时,可以轻松地将整个文件或全局列入白名单。与我们的settings.purgeCssConfig.whitelist匹配的所有文件中的CSS规则都列入白名单,并且永远不会从生成的构建中删除。

接下来是configureTerser()

// Configure terserconst configureTerser = () => {    return {        cache: true,        parallel: true,        sourceMap: true    };};复制代码

这只是配置了[TerserPlugin] ()使用的一些设置,最大限度地减少了我们的旧版和新版JavaScript代码。

接着是configureWebApp()

// Configure Webapp webpackconst configureWebapp = () => {    return {        logo: settings.webappConfig.logo,        prefix: settings.webappConfig.prefix,        cache: false,        inject: 'force',        favicons: {            appName: pkg.name,            appDescription: pkg.description,            developerName: pkg.author.name,            developerURL: pkg.author.url,            path: settings.paths.dist.base,        }    };};复制代码

这里使用以无数种格式生成我们所有的网站favicon,以及我们的webapp manifest.json和其他PWA细节。

它与HtmlWebpackPlugin结合使用,还可以输出一个webapp.html文件,它包含所有生成的favicons和相关文件的链接,以包含在我们的HTML页面的<head></head>中。

接着是configureWorkbox()

// Configure Workbox service workerconst configureWorkbox = () => {    let config = settings.workboxConfig;    return config;};复制代码

我们使用Google的为网站生成一个,解释 Service Worker是什么超出了本文的内容范围,但可以查看博客作为入门。

配置数据全部来自webpack.settings.js中的settings.workboxConfig对象。除了预先缓存新版构建minifest.json中所有的资源外,我们还包括一个workbox-catch-handler.js来配置它以使。

// fallback URLsconst FALLBACK_HTML_URL = '/offline.html';const FALLBACK_IMAGE_URL = '/offline.svg';// This "catch" handler is triggered when any of the other routes fail to// generate a response.// https://developers.google.com/web/tools/workbox/guides/advanced-recipes#provide_a_fallback_response_to_a_routeworkbox.routing.setCatchHandler(({event, request, url}) => {    // Use event, request, and url to figure out how to respond.    // One approach would be to use request.destination, see    // https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c    switch (request.destination) {        case 'document':            return caches.match(FALLBACK_HTML_URL);            break;        case 'image':            return caches.match(FALLBACK_IMAGE_URL);            break;        default:            // If we don't have a fallback, just return an error response.            return Response.error();    }});// Use a stale-while-revalidate strategy for all other requests.workbox.routing.setDefaultHandler(    workbox.strategies.staleWhileRevalidate());复制代码

MODULE.EXPORTS

最后,module.export使用webpack-mergewebpack.commons.js中的common.legacyConfig与我们的生产环境旧版配置合并,并将common.modernConfig与我们的生产环境新版配置合并:

// Production module exportsmodule.exports = [    merge(        common.legacyConfig,        {            output: {                filename: path.join('./js', '[name]-legacy.[chunkhash].js'),            },            mode: 'production',            devtool: 'source-map',            optimization: configureOptimization(LEGACY_CONFIG),            module: {                rules: [                    configurePostcssLoader(LEGACY_CONFIG),                    configureImageLoader(LEGACY_CONFIG),                ],            },            plugins: [                new CleanWebpackPlugin(settings.paths.dist.clean,                    configureCleanWebpack()                ),                new MiniCssExtractPlugin({                    path: path.resolve(__dirname, settings.paths.dist.base),                    filename: path.join('./css', '[name].[chunkhash].css'),                }),                new PurgecssPlugin(                    configurePurgeCss()                ),                new webpack.BannerPlugin(                    configureBanner()                ),                new HtmlWebpackPlugin(                    configureHtml()                ),                new WebappWebpackPlugin(                    configureWebapp()                ),                new CreateSymlinkPlugin(                    settings.createSymlinkConfig,                    true                ),                new SaveRemoteFilePlugin(                    settings.saveRemoteFileConfig                ),                new BundleAnalyzerPlugin(                    configureBundleAnalyzer(LEGACY_CONFIG),                ),            ].concat(                configureCriticalCss()            )        }    ),    merge(        common.modernConfig,        {            output: {                filename: path.join('./js', '[name].[chunkhash].js'),            },            mode: 'production',            devtool: 'source-map',            optimization: configureOptimization(MODERN_CONFIG),            module: {                rules: [                    configurePostcssLoader(MODERN_CONFIG),                    configureImageLoader(MODERN_CONFIG),                ],            },            plugins: [                new webpack.optimize.ModuleConcatenationPlugin(),                new webpack.BannerPlugin(                    configureBanner()                ),                new ImageminWebpWebpackPlugin(),                new WorkboxPlugin.GenerateSW(                    configureWorkbox()                ),                new BundleAnalyzerPlugin(                    configureBundleAnalyzer(MODERN_CONFIG),                ),            ]        }    ),];复制代码

通过在我们的module.exports中返回一个数组,我们告诉webpack有多个需要完成的编译:一个用于旧版兼容构建,另一个用于新版构建。

注意,对于旧版兼容构建,我们将处理后的JavaScript输出为[name]-legacy.[hash].js,而新版构建将其输出为[name].[hash].js

通过将mode设置为production,我们告知webpack这是一个,这将启用许多适用于生产环境的设置。

通过将devtool设置为source-map,我们要求将CSS/JavaScript,这是我们更容易调试实时生产环境网站,而无需添加资源的文件大小。

这里使用了几个我们尚未涉及的webpack插件:

  • —— 这是我创建的一个插件,允许在构建过程中创建符号链接,使用它来将生成的favicon.ico符号链接到/favicon.ico,因为许多web浏览器在web根目录中查找。

  • —— 用于下载远程文件并将其作为webpack构建过程的一部分输出。我用它来下载和提供谷歌的分析。

  • —— 此插件会为项目导入的所有JPEG和PNG文件创建.webp变体。

直到现在,我们为项目提供了一个很好的生产环境构建。

TAILWIND CSS & POSTCSS CONFIG

为了使webpack正确构建Tailwind CSS和其他css,我们需要做一些设置,感谢我的伙伴Jonathan Melville在构建这方面的工作,首先我们需要一个postcss.config.js文件:

module.exports = {    plugins: [        require('postcss-import'),        require('postcss-extend'),        require('postcss-simple-vars'),        require('postcss-nested-ancestors'),        require('postcss-nested'),        require('postcss-hexrgba'),        require('autoprefixer'),        require('tailwindcss')('./tailwind.config.js')    ]};复制代码

这可以存储在项目根目录中,PostCSS将在构建过程中自动查找它,并应用我们指定的PostCSS插件。请注意,这是我们引入tailwind.config.js文件的位置,以便其成为构建过程的一部分。

最后,我们的CSS入口点app.pcss看起来像这样:

/** * app.css * * The entry point for the css. * *//** * This injects Tailwind's base styles, which is a combination of * Normalize.css and some additional base styles. * * You can see the styles here: * https://github.com/tailwindcss/tailwindcss/blob/master/css/preflight.css */ @import "tailwindcss/preflight";/** * This injects any component classes registered by plugins. * */@import 'tailwindcss/components';/** * Here we add custom component classes; stuff we want loaded * *before* the utilities so that the utilities can still * override them. * */@import './components/global.pcss';@import './components/typography.pcss';@import './components/webfonts.pcss';/** * This injects all of Tailwind's utility classes, generated based on your * config file. * */@import 'tailwindcss/utilities';/** * Include styles for individual pages * */@import './pages/homepage.pcss';/** * Include vendor css. * */ @import 'vendor.pcss';复制代码

显然,对其进行定制以包括用于自定义css的任何组件/界面。

POST-BUILD PROJECT TREE

这是我们项目在构建后的结构:

├── 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├── web│   ├── dist│   │   ├── criticalcss│   │   │   └── index_critical.min.css│   │   ├── css│   │   │   ├── styles.d833997e3e3f91af64e7.css│   │   │   └── styles.d833997e3e3f91af64e7.css.map│   │   ├── img│   │   │   └── favicons│   │   │       ├── android-chrome-144x144.png│   │   │       ├── android-chrome-192x192.png│   │   │       ├── android-chrome-256x256.png│   │   │       ├── android-chrome-36x36.png│   │   │       ├── android-chrome-384x384.png│   │   │       ├── android-chrome-48x48.png│   │   │       ├── android-chrome-512x512.png│   │   │       ├── android-chrome-72x72.png│   │   │       ├── android-chrome-96x96.png│   │   │       ├── apple-touch-icon-114x114.png│   │   │       ├── apple-touch-icon-120x120.png│   │   │       ├── apple-touch-icon-144x144.png│   │   │       ├── apple-touch-icon-152x152.png│   │   │       ├── apple-touch-icon-167x167.png│   │   │       ├── apple-touch-icon-180x180.png│   │   │       ├── apple-touch-icon-57x57.png│   │   │       ├── apple-touch-icon-60x60.png│   │   │       ├── apple-touch-icon-72x72.png│   │   │       ├── apple-touch-icon-76x76.png│   │   │       ├── apple-touch-icon.png│   │   │       ├── apple-touch-icon-precomposed.png│   │   │       ├── apple-touch-startup-image-1182x2208.png│   │   │       ├── apple-touch-startup-image-1242x2148.png│   │   │       ├── apple-touch-startup-image-1496x2048.png│   │   │       ├── apple-touch-startup-image-1536x2008.png│   │   │       ├── apple-touch-startup-image-320x460.png│   │   │       ├── apple-touch-startup-image-640x1096.png│   │   │       ├── apple-touch-startup-image-640x920.png│   │   │       ├── apple-touch-startup-image-748x1024.png│   │   │       ├── apple-touch-startup-image-750x1294.png│   │   │       ├── apple-touch-startup-image-768x1004.png│   │   │       ├── browserconfig.xml│   │   │       ├── coast-228x228.png│   │   │       ├── favicon-16x16.png│   │   │       ├── favicon-32x32.png│   │   │       ├── favicon.ico│   │   │       ├── firefox_app_128x128.png│   │   │       ├── firefox_app_512x512.png│   │   │       ├── firefox_app_60x60.png│   │   │       ├── manifest.json│   │   │       ├── manifest.webapp│   │   │       ├── mstile-144x144.png│   │   │       ├── mstile-150x150.png│   │   │       ├── mstile-310x150.png│   │   │       ├── mstile-310x310.png│   │   │       ├── mstile-70x70.png│   │   │       ├── yandex-browser-50x50.png│   │   │       └── yandex-browser-manifest.json│   │   ├── js│   │   │   ├── analytics.45eff9ff7d6c7c1e3c3d4184fdbbed90.js│   │   │   ├── app.30334b5124fa6e221464.js│   │   │   ├── app.30334b5124fa6e221464.js.map│   │   │   ├── app-legacy.560ef247e6649c0c24d0.js│   │   │   ├── app-legacy.560ef247e6649c0c24d0.js.map│   │   │   ├── confetti.1152197f8c58a1b40b34.js│   │   │   ├── confetti.1152197f8c58a1b40b34.js.map│   │   │   ├── confetti-legacy.8e9093b414ea8aed46e5.js│   │   │   ├── confetti-legacy.8e9093b414ea8aed46e5.js.map│   │   │   ├── precache-manifest.f774c437974257fc8026ca1bc693655c.js│   │   │   ├── styles-legacy.d833997e3e3f91af64e7.js│   │   │   ├── styles-legacy.d833997e3e3f91af64e7.js.map│   │   │   ├── vendors~confetti~vue.03b9213ce186db5518ea.js│   │   │   ├── vendors~confetti~vue.03b9213ce186db5518ea.js.map│   │   │   ├── vendors~confetti~vue-legacy.e31223849ab7fea17bb8.js│   │   │   ├── vendors~confetti~vue-legacy.e31223849ab7fea17bb8.js.map│   │   │   └── workbox-catch-handler.js│   │   ├── manifest.json│   │   ├── manifest-legacy.json│   │   ├── report-legacy.html│   │   ├── report-modern.html│   │   ├── webapp.html│   │   └── workbox-catch-handler.js│   ├── favicon.ico -> dist/img/favicons/favicon.ico│   ├── index.php│   ├── offline.html│   ├── offline.svg│   └── sw.js├── webpack.common.js├── webpack.dev.js├── webpack.prod.js├── webpack.settings.js└── yarn.lock复制代码

INJECTING SCRIPT & CSS TAGS IN YOUR HTML

通过这里显示的webpack配置,<script><style>不会作为生产构建的一部分注入到HTML中,该设置使用,它具有模板系统,我们使用插件注入标签。

如果你没有使用Craft CMS或具有模板引擎的系统,并且希望将这些标记注入到HTML中,那么需要使用执行此操作,这个配置已经包含在内,你只需要添加一个配置来告诉它将标签注入到HTML。

CRAFT CMS 3 INTEGRATION WITH THE TWIGPACK PLUGIN

如果你没有使用,可以跳过这一部分,它只是提供了一些有用的集成信息。

我写了一个叫的免费插件,可以很容易地将我们的webpack构建设置与Craft CMS 3集成。

它处理manifest.json文件以将入口点注入到Twig模板中,甚至用于处理执行旧版/新版模块注入,异步css加载以及更多的模式。

它将使这里介绍的webpack4配置非常简单。

为了包含CSS,我这样做:

{
{ craft.twigpack.includeCssModule("styles.css", false) }}
{
{ craft.twigpack.includeCriticalCssTags() }} {
{ craft.twigpack.includeCssModule("styles.css", true) }} {
{ craft.twigpack.includeCssRelPreloadPolyfill() }}
复制代码

<!--#-->HTML注释是指令,模式是如果设置了critical-css cookie,用户已经在过去7天访问过我们的网站,那么他们的浏览器应该有网站css缓存,我们只是正常提供网站css。

如果没有设置critical-css cookie,我们通过设置cookie,包括我们的Critical CSS,并异步加载站点CSS。有关Critical CSS的详细信息,可以参考文章。

为了提供我们的javascript,我们只需执行以下操作:

{
{ craft.twigpack.includeSafariNomoduleFix() }}{
{ craft.twigpack.includeJsModule("app.js", true) }}复制代码

第二个参数true告诉它将JavaScript异步模块加载,因此生成的HTML如下所示:

复制代码

有关详细介绍,请查看文档。

这是我使用的完整config/twigpack.php文件,请注意,它具有我在Homestead VM内部运行的本地设置,与你的设置可能不同:

return [    // Global settings    '*' => [        // If `devMode` is on, use webpack-dev-server to all for HMR (hot module reloading)        'useDevServer' => false,        // The JavaScript entry from the manifest.json to inject on Twig error pages        'errorEntry' => '',        // Manifest file names        'manifest' => [            'legacy' => 'manifest-legacy.json',            'modern' => 'manifest.json',        ],        // Public server config        'server' => [            'manifestPath' => '/dist/',            'publicPath' => '/',        ],        // webpack-dev-server config        'devServer' => [            'manifestPath' => 'http://localhost:8080/',            'publicPath' => 'http://localhost:8080/',        ],        // Local files config        'localFiles' => [            'basePath' => '@webroot/',            'criticalPrefix' => 'dist/criticalcss/',            'criticalSuffix' => '_critical.min.css',        ],    ],    // Live (production) environment    'live' => [    ],    // Staging (pre-production) environment    'staging' => [    ],    // Local (development) environment    'local' => [        // If `devMode` is on, use webpack-dev-server to all for HMR (hot module reloading)        'useDevServer' => true,        // The JavaScript entry from the manifest.json to inject on Twig error pages        'errorEntry' => 'app.js',        // webpack-dev-server config        'devServer' => [            'manifestPath' => 'http://localhost:8080/',            'publicPath' => 'http://192.168.10.10:8080/',        ],    ],];复制代码

WRAPPING UP!

哇,这是一个深坑,当我第一次开始研究webpack时,我很快意识到它是一个非常强大的工具,具有非常强大的功能。你走多远取决于你想要游到多深。

有关本篇文章的完整源代码,请查看仓库。

希望这篇文章对你有所帮助,慢慢消化,将它做的更棒。

FURTHER READING

如果你想收到有关新文章的通知,请在推特上关注。

转载地址:http://etjpo.baihongyu.com/

你可能感兴趣的文章
angular项目中bootstrap-datetimepicker时间插件的使用
查看>>
通过网络仓库建立本地的yum仓库
查看>>
【web端权限维持】利用ADS隐藏webshell
查看>>
Linux下gdb的安装及使用入门
查看>>
Java 程序执行过程的内存分析
查看>>
灾难恢复-boot分区的恢复方法
查看>>
小游戏-猜数字
查看>>
深度学习到顶,AI寒冬将至!
查看>>
【投资】欧盟区块链创业公司投资超500万欧元
查看>>
优傲机器人:人机协作机器人助推电子制造业智慧升级
查看>>
PHP浮点数的精确计算BCMath
查看>>
[起重机监测系统] 1、基于无线传输的桥式起重机的安全监测方案
查看>>
2014年发展计划
查看>>
QQ协议
查看>>
[Android]一个干净的架构(翻译)
查看>>
Oracle RAC安装过程中碰到的“坑”和关键点(一)
查看>>
Jmeter关联
查看>>
java的nio之:java的nio系列教程之Scatter/Gather
查看>>
linux命令之ldconfig
查看>>
Shell之sed命令
查看>>