├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── demo.txt
├── loaders
├── demo-loader.js
└── pitch-loader.js
├── messages
├── en.json
└── fi.json
├── package-lock.json
├── package.json
├── plugins
├── demo-plugin.js
├── test-entry.js
└── test.js
├── run-loader.js
├── server.js
├── src
├── bootstrap.js
├── component.js
├── header.js
├── i18n.js
├── index.js
├── lazy.js
├── logo.png
├── main.css
├── mf.js
├── multi.js
├── shake.js
├── ssr.js
└── worker.js
├── tailwind.config.js
├── tests
├── add.js
├── add.test.js
└── index.js
├── webpack.config.js
├── webpack.i18n.js
├── webpack.mf.js
├── webpack.multi.js
├── webpack.parts.js
└── webpack.ssr.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | [
5 | "@babel/preset-env",
6 | {
7 | "modules": false
8 | }
9 | ]
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1% # Browser usage over 1%
2 | Last 2 versions # Or last two versions
3 | IE 8 # Or IE 8
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # General settings for whole project
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | # Format specific overrides
13 | [*.js]
14 | indent_style = space
15 | indent_size = 2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | i18n-build/
4 | static/
5 | records.json
6 | stats.json
7 |
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Juho Vepsäläinen
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webpack-demo - Webpack demo for "SurviveJS - Webpack 5"
2 |
3 | See [SurviveJS - Webpack 5](http://survivejs.com/webpack/introduction/) to learn how/why this configuration works.
4 |
5 | ## License
6 |
7 | MIT.
8 |
--------------------------------------------------------------------------------
/demo.txt:
--------------------------------------------------------------------------------
1 | {}
2 |
3 |
--------------------------------------------------------------------------------
/loaders/demo-loader.js:
--------------------------------------------------------------------------------
1 | const loaderUtils = require("loader-utils");
2 |
3 | module.exports = function (content) {
4 | const { name } = this.getOptions();
5 | const url = loaderUtils.interpolateName(this, name, { content });
6 |
7 | this.emitFile(url, content);
8 |
9 | const path = `__webpack_public_path__ + ${JSON.stringify(url)};`;
10 |
11 | return `export default ${path}`;
12 | };
13 |
--------------------------------------------------------------------------------
/loaders/pitch-loader.js:
--------------------------------------------------------------------------------
1 | module.exports = function (input) {
2 | return input + this.getOptions().text;
3 | };
4 | module.exports.pitch = function (remaining, preceding, input) {
5 | console.log(`Remaining: ${remaining}, preceding: ${preceding}
6 | Input: ${JSON.stringify(input, null, 2)}
7 | `);
8 |
9 | return "pitched";
10 | };
11 |
--------------------------------------------------------------------------------
/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "hello": "Hello world"
3 | }
4 |
--------------------------------------------------------------------------------
/messages/fi.json:
--------------------------------------------------------------------------------
1 | {
2 | "hello": "Terve maailma"
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-demo",
3 | "version": "3.0.8",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "deploy": "gh-pages -d dist",
8 | "build:i18n": "wp --config webpack.i18n.js",
9 | "build:ssr": "wp --config webpack.ssr.js",
10 | "build:multi": "wp --config webpack.multi.js",
11 | "build:stats": "wp --mode production --json > stats.json",
12 | "build:mf": "wp --config webpack.mf.js --mode production",
13 | "start:mf": "wp --config webpack.mf.js --mode development",
14 | "start": "wp --mode development",
15 | "build": "wp --mode production"
16 | },
17 | "keywords": [],
18 | "author": "",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "@babel/core": "^7.17.8",
22 | "@babel/preset-env": "^7.16.11",
23 | "@babel/preset-react": "^7.16.7",
24 | "autoprefixer": "^10.4.4",
25 | "babel-loader": "^8.2.3",
26 | "browser-refresh": "^1.7.3",
27 | "css-loader": "^6.7.1",
28 | "css-minimizer-webpack-plugin": "^3.4.1",
29 | "express": "^4.17.3",
30 | "gh-pages": "^3.2.3",
31 | "git-revision-webpack-plugin": "^5.0.0",
32 | "glob": "^7.2.0",
33 | "loader-runner": "^4.2.0",
34 | "memfs": "^3.4.1",
35 | "mini-css-extract-plugin": "^2.6.0",
36 | "mini-html-webpack-plugin": "^3.1.3",
37 | "postcss-loader": "^6.2.1",
38 | "prettier": "^2.6.0",
39 | "purgecss-webpack-plugin": "^4.1.3",
40 | "tailwindcss": "^3.0.23",
41 | "terser-webpack-plugin": "^5.3.1",
42 | "webpack": "^5.70.0",
43 | "webpack-merge": "^5.8.0",
44 | "webpack-nano": "^1.1.1",
45 | "webpack-plugin-serve": "^1.6.0"
46 | },
47 | "dependencies": {
48 | "react": "^17.0.2",
49 | "react-dom": "^17.0.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/plugins/demo-plugin.js:
--------------------------------------------------------------------------------
1 | const { sources, Compilation } = require("webpack");
2 |
3 | module.exports = class DemoPlugin {
4 | constructor(options) {
5 | this.options = options;
6 | }
7 | apply(compiler) {
8 | const pluginName = "DemoPlugin";
9 | const { name } = this.options;
10 |
11 | compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
12 | compilation.hooks.processAssets.tap(
13 | {
14 | name: pluginName,
15 | // See lib/Compilation.js in webpack to understand different stages
16 | stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
17 | },
18 | () => compilation.emitAsset(name, new sources.RawSource("hello", true))
19 | );
20 | });
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/plugins/test-entry.js:
--------------------------------------------------------------------------------
1 | console.log("hello from entry");
--------------------------------------------------------------------------------
/plugins/test.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const { createFsFromVolume, Volume } = require("memfs");
3 |
4 | // The compiler helper accepts filenames should be in the output
5 | // so it's possible to assert the output easily.
6 | function compile(config, filenames = []) {
7 | return new Promise((resolve, reject) => {
8 | const compiler = webpack(config);
9 | compiler.outputFileSystem = createFsFromVolume(new Volume());
10 | const memfs = compiler.outputFileSystem;
11 |
12 | compiler.run((err, stats) => {
13 | if (err) {
14 | return reject(err);
15 | }
16 |
17 | // Now only errors are captured from stats.
18 | // It's possible to capture more to assert.
19 | if (stats.hasErrors()) {
20 | return reject(stats.toString("errors-only"));
21 | }
22 |
23 | const ret = {};
24 | filenames.forEach((filename) => {
25 | // The assumption is that webpack outputs behind ./dist.
26 | ret[filename] = memfs.readFileSync(`./dist/${filename}`, {
27 | encoding: "utf-8",
28 | });
29 | });
30 | return resolve(ret);
31 | });
32 | });
33 | }
34 |
35 | async function test() {
36 | console.log(
37 | await compile({
38 | entry: "./test-entry.js",
39 | }),
40 | ["demo"]
41 | );
42 | }
43 |
44 | test();
45 |
--------------------------------------------------------------------------------
/run-loader.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 | const { runLoaders } = require("loader-runner");
4 |
5 | runLoaders(
6 | {
7 | resource: "./demo.txt",
8 | loaders: [
9 | {
10 | loader: path.resolve(__dirname, "./loaders/demo-loader"),
11 | options: {
12 | name: "demo.[ext]",
13 | },
14 | },
15 | path.resolve(__dirname, "./loaders/pitch-loader"),
16 | ],
17 | context: {
18 | emitFile: () => {},
19 | },
20 | readResource: fs.readFile.bind(fs),
21 | },
22 | (err, result) => (err ? console.error(err) : console.log(result))
23 | );
24 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const { renderToString } = require("react-dom/server");
3 |
4 | const SSR = require("./static");
5 |
6 | server(parseInt(process.env.PORT, 10) || 8080);
7 |
8 | function server(port) {
9 | const app = express();
10 |
11 | app.use(express.static("static"));
12 | app.get("/", (req, res) =>
13 | res.status(200).send(renderMarkup(renderToString(SSR)))
14 | );
15 |
16 | app.listen(port, () => process.send && process.send("online"));
17 | }
18 |
19 | function renderMarkup(html) {
20 | return `
21 |
22 |
23 | Webpack SSR Demo
24 |
25 |
26 |
27 | ${html}
28 |
29 |
30 |
31 | `;
32 | }
33 |
--------------------------------------------------------------------------------
/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | import("./mf");
2 |
--------------------------------------------------------------------------------
/src/component.js:
--------------------------------------------------------------------------------
1 | import "!demo-loader?name=foo!./main.css";
2 |
3 | export default (text = HELLO) => {
4 | const element = document.createElement("h1");
5 | const worker = new Worker(new URL("./worker.js", import.meta.url));
6 | const state = { text };
7 |
8 | worker.addEventListener("message", ({ data: { text } }) => {
9 | state.text = text;
10 | element.innerHTML = text;
11 | });
12 |
13 | element.innerHTML = state.text;
14 | element.onclick = () => worker.postMessage({ text: state.text });
15 |
16 | return element;
17 | };
18 |
--------------------------------------------------------------------------------
/src/header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | const Header = () => Demo
;
3 | export default Header;
4 |
--------------------------------------------------------------------------------
/src/i18n.js:
--------------------------------------------------------------------------------
1 | import "regenerator-runtime/runtime";
2 | import React, { useEffect, useState } from "react";
3 | import ReactDOM from "react-dom";
4 |
5 | const App = () => {
6 | const [language, setLanguage] = useState("en");
7 | const [hello, setHello] = useState("");
8 | const changeLanguage = () => setLanguage(language === "en" ? "fi" : "en");
9 |
10 | useEffect(() => {
11 | translate(language, "hello").then(setHello).catch(console.error);
12 | }, [language]);
13 |
14 | return (
15 |
16 |
17 |
{hello}
18 |
19 | );
20 | };
21 |
22 | function translate(locale, text) {
23 | return getLocaleData(locale).then((messages) => messages[text]);
24 | }
25 |
26 | async function getLocaleData(locale) {
27 | return import(`../messages/${locale}.json`);
28 | }
29 |
30 | const root = document.createElement("div");
31 | root.setAttribute("id", "app");
32 | document.body.appendChild(root);
33 | ReactDOM.render(, root);
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import "react";
2 | import "react-dom";
3 | import "./main.css";
4 | import component from "./component";
5 | import { bake } from "./shake";
6 |
7 | bake();
8 |
9 | document.body.appendChild(component());
10 |
--------------------------------------------------------------------------------
/src/lazy.js:
--------------------------------------------------------------------------------
1 | export default "Hello from lazy";
2 |
--------------------------------------------------------------------------------
/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/survivejs-demos/webpack-demo/0180bacbee06d96255f9e945136aadeada918290/src/logo.png
--------------------------------------------------------------------------------
/src/main.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 |
4 | /* Write your utility classes here */
5 |
6 | @tailwind utilities;
7 |
8 | body {
9 | background: cornsilk;
10 | background-image: url("./logo.png");
11 | background-repeat: no-repeat;
12 | background-position: center;
13 | }
14 |
--------------------------------------------------------------------------------
/src/mf.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from "react-dom";
2 | import React from "react";
3 | import "./main.css";
4 | import Header from "mf/header";
5 |
6 | function App() {
7 | const options = ["Hello world", "Hello fed", "Hello webpack"];
8 | const [content, setContent] = React.useState("Changes on click.");
9 |
10 | return (
11 |
12 |
13 |
27 | {content}
28 |
29 | );
30 | }
31 |
32 | const container = document.createElement("div");
33 | document.body.appendChild(container);
34 | ReactDOM.render(, container);
35 |
--------------------------------------------------------------------------------
/src/multi.js:
--------------------------------------------------------------------------------
1 | const element = document.createElement("div");
2 | element.innerHTML = "hello multi";
3 | document.body.appendChild(element);
4 |
--------------------------------------------------------------------------------
/src/shake.js:
--------------------------------------------------------------------------------
1 | const shake = () => console.log("shake");
2 | const bake = () => console.log("bake");
3 |
4 | export { shake, bake };
--------------------------------------------------------------------------------
/src/ssr.js:
--------------------------------------------------------------------------------
1 | const React = require("react");
2 | const ReactDOM = require("react-dom");
3 |
4 | const SSR = alert("hello")}>Hello world
;
5 |
6 | // Render only in the browser, export otherwise
7 | if (typeof document === "undefined") {
8 | module.exports = SSR;
9 | } else {
10 | ReactDOM.hydrate(SSR, document.getElementById("app"));
11 | }
12 |
--------------------------------------------------------------------------------
/src/worker.js:
--------------------------------------------------------------------------------
1 | self.onmessage = ({ data: { text } }) => {
2 | self.postMessage({ text: text + text });
3 | };
4 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js}"],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | };
8 |
--------------------------------------------------------------------------------
/tests/add.js:
--------------------------------------------------------------------------------
1 | module.exports = (a, b) => a + b;
2 |
--------------------------------------------------------------------------------
/tests/add.test.js:
--------------------------------------------------------------------------------
1 | const assert = require("assert");
2 | const add = require("./add");
3 |
4 | describe("Demo", () => {
5 | it("should add correctly", () => {
6 | assert.equal(add(1, 1), 2);
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | // Skip execution in Node
2 | if (module.hot) {
3 | const context = require.context(
4 | "mocha-loader!./", // Process through mocha-loader
5 | false, // Skip recursive processing
6 | /\.test.js$/ // Pick only files ending with .test.js
7 | );
8 |
9 | // Execute each test suite
10 | context.keys().forEach(context);
11 | }
12 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { mode } = require("webpack-nano/argv");
2 | const { merge } = require("webpack-merge");
3 | const path = require("path");
4 |
5 | const parts = require("./webpack.parts");
6 |
7 | const cssLoaders = [parts.autoprefix(), parts.tailwind()];
8 |
9 | const commonConfig = merge([
10 | {
11 | resolveLoader: {
12 | alias: {
13 | "demo-loader": path.resolve(__dirname, "loaders/demo-loader.js"),
14 | },
15 | },
16 | },
17 | {
18 | output: {
19 | // Tweak this to match your GitHub project name
20 | publicPath: "/",
21 | },
22 | },
23 | { entry: ["./src"] },
24 | parts.page({ title: "Demo" }),
25 | parts.clean(),
26 | parts.extractCSS({ loaders: cssLoaders }),
27 | parts.loadImages({
28 | limit: 15000,
29 | }),
30 | parts.loadJavaScript(),
31 | parts.setFreeVariable("HELLO", "hello from config"),
32 | ]);
33 |
34 | const productionConfig = merge([
35 | {
36 | output: {
37 | chunkFilename: "[name].[contenthash].js",
38 | filename: "[name].[contenthash].js",
39 | assetModuleFilename: "[name].[contenthash][ext][query]",
40 | },
41 | recordsPath: path.join(__dirname, "records.json"),
42 | },
43 | parts.minifyJavaScript(),
44 | parts.minifyCSS({
45 | options: {
46 | preset: ["default"],
47 | },
48 | }),
49 | parts.eliminateUnusedCSS(),
50 | parts.generateSourceMaps({ type: "source-map" }),
51 | {
52 | optimization: {
53 | splitChunks: {
54 | chunks: "all",
55 | },
56 | runtimeChunk: {
57 | name: "runtime",
58 | },
59 | },
60 | },
61 | parts.attachRevision(),
62 | ]);
63 |
64 | const developmentConfig = merge([parts.devServer()]);
65 |
66 | const getConfig = (mode) => {
67 | switch (mode) {
68 | case "production":
69 | return merge(commonConfig, productionConfig, { mode });
70 | case "development":
71 | return merge(commonConfig, developmentConfig, { mode });
72 | default:
73 | throw new Error(`Trying to use an unknown mode, ${mode}`);
74 | }
75 | };
76 |
77 | module.exports = getConfig(mode);
78 |
--------------------------------------------------------------------------------
/webpack.i18n.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { MiniHtmlWebpackPlugin } = require("mini-html-webpack-plugin");
3 | const APP_SOURCE = path.join(__dirname, "src");
4 |
5 | module.exports = {
6 | mode: "production",
7 | entry: { index: path.join(APP_SOURCE, "i18n.js") },
8 | module: {
9 | rules: [
10 | {
11 | test: /\.js$/,
12 | include: APP_SOURCE,
13 | use: "babel-loader",
14 | },
15 | ],
16 | },
17 | plugins: [new MiniHtmlWebpackPlugin()],
18 | };
19 |
--------------------------------------------------------------------------------
/webpack.mf.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { component, mode } = require("webpack-nano/argv");
3 | const { merge } = require("webpack-merge");
4 | const parts = require("./webpack.parts");
5 |
6 | const commonConfig = merge([
7 | {
8 | output: { publicPath: "/" },
9 | },
10 | parts.loadJavaScript(),
11 | parts.loadImages(),
12 | parts.page(),
13 | parts.extractCSS({ loaders: [parts.tailwind()] }),
14 | ]);
15 |
16 | const configs = {
17 | development: merge(
18 | { entry: ["webpack-plugin-serve/client"] },
19 | parts.devServer()
20 | ),
21 | production: {},
22 | };
23 |
24 | const shared = {
25 | react: { singleton: true },
26 | "react-dom": { singleton: true },
27 | };
28 | const componentConfigs = {
29 | app: merge(
30 | {
31 | entry: [path.join(__dirname, "src", "bootstrap.js")],
32 | },
33 | parts.page(),
34 | parts.federateModule({
35 | name: "app",
36 | remotes: { mf: "mf@/mf.js" },
37 | shared,
38 | })
39 | ),
40 | header: merge(
41 | {
42 | entry: [path.join(__dirname, "src", "header.js")],
43 | },
44 | parts.federateModule({
45 | name: "mf",
46 | filename: "mf.js",
47 | exposes: { "./header": "./src/header" },
48 | shared,
49 | })
50 | ),
51 | };
52 |
53 | if (!component) throw new Error("Missing component name");
54 |
55 | module.exports = merge(
56 | commonConfig,
57 | configs[mode],
58 | { mode },
59 | componentConfigs[component]
60 | );
61 |
--------------------------------------------------------------------------------
/webpack.multi.js:
--------------------------------------------------------------------------------
1 | const { merge } = require("webpack-merge");
2 | const parts = require("./webpack.parts");
3 |
4 | module.exports = merge(
5 | { mode: "production", entry: { app: "./src/multi.js" } },
6 | parts.page({ title: "Demo" }),
7 | parts.page({ title: "Another", url: "another" })
8 | );
9 |
--------------------------------------------------------------------------------
/webpack.parts.js:
--------------------------------------------------------------------------------
1 | const { WebpackPluginServe } = require("webpack-plugin-serve");
2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3 | const path = require("path");
4 | const glob = require("glob");
5 | const PurgeCSSPlugin = require("purgecss-webpack-plugin");
6 | const webpack = require("webpack");
7 | const { GitRevisionPlugin } = require("git-revision-webpack-plugin");
8 | const TerserPlugin = require("terser-webpack-plugin");
9 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
10 | const { MiniHtmlWebpackPlugin } = require("mini-html-webpack-plugin");
11 | const { ModuleFederationPlugin } = require("webpack").container;
12 |
13 | const ALL_FILES = glob.sync(path.join(__dirname, "src/*.js"));
14 | const APP_SOURCE = path.join(__dirname, "src");
15 |
16 | exports.federateModule = ({ name, filename, exposes, remotes, shared }) => ({
17 | plugins: [
18 | new ModuleFederationPlugin({ name, filename, exposes, remotes, shared }),
19 | ],
20 | });
21 |
22 | exports.page = ({ path = "", template, title, chunks } = {}) => ({
23 | plugins: [
24 | new MiniHtmlWebpackPlugin({
25 | chunks,
26 | filename: `${path && path + "/"}index.html`,
27 | context: { title },
28 | template,
29 | }),
30 | ],
31 | });
32 |
33 | exports.setFreeVariable = (key, value) => {
34 | const env = {};
35 | env[key] = JSON.stringify(value);
36 |
37 | return {
38 | plugins: [new webpack.DefinePlugin(env)],
39 | };
40 | };
41 |
42 | exports.minifyCSS = ({ options }) => ({
43 | optimization: {
44 | minimizer: [new CssMinimizerPlugin({ minimizerOptions: options })],
45 | },
46 | });
47 |
48 | exports.minifyJavaScript = () => ({
49 | optimization: {
50 | minimizer: [new TerserPlugin()],
51 | },
52 | });
53 |
54 | exports.attachRevision = () => ({
55 | plugins: [
56 | new webpack.BannerPlugin({
57 | banner: new GitRevisionPlugin().version(),
58 | }),
59 | ],
60 | });
61 |
62 | exports.clean = () => ({
63 | output: {
64 | clean: true,
65 | },
66 | });
67 |
68 | exports.loadJavaScript = () => ({
69 | module: {
70 | rules: [
71 | {
72 | test: /\.js$/,
73 | include: APP_SOURCE, // Consider extracting as a parameter
74 | use: "babel-loader",
75 | },
76 | ],
77 | },
78 | });
79 |
80 | exports.eliminateUnusedCSS = () => ({
81 | plugins: [
82 | new PurgeCSSPlugin({
83 | paths: ALL_FILES, // Consider extracting as a parameter
84 | extractors: [
85 | {
86 | extractor: (content) =>
87 | content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [],
88 | extensions: ["html"],
89 | },
90 | ],
91 | }),
92 | ],
93 | });
94 |
95 | exports.extractCSS = ({ options = {}, loaders = [] } = {}) => {
96 | return {
97 | module: {
98 | rules: [
99 | {
100 | test: /\.css$/,
101 | use: [
102 | { loader: MiniCssExtractPlugin.loader, options },
103 | "css-loader",
104 | ].concat(loaders),
105 | // If you distribute your code as a package and want to
106 | // use _Tree Shaking_, then you should mark CSS extraction
107 | // to emit side effects. For most use cases, you don't
108 | // have to worry about setting flag.
109 | sideEffects: true,
110 | },
111 | ],
112 | },
113 | plugins: [
114 | new MiniCssExtractPlugin({
115 | filename: "[name].[contenthash].css",
116 | }),
117 | ],
118 | };
119 | };
120 |
121 | exports.devServer = () => ({
122 | watch: true,
123 | plugins: [
124 | new WebpackPluginServe({
125 | port: parseInt(process.env.PORT, 10) || 8080,
126 | static: "./dist", // Expose if output.path changes
127 | liveReload: true,
128 | waitForBuild: true,
129 | }),
130 | ],
131 | });
132 |
133 | exports.tailwind = () => ({
134 | loader: "postcss-loader",
135 | options: {
136 | postcssOptions: {
137 | plugins: [require("tailwindcss")()],
138 | },
139 | },
140 | });
141 |
142 | exports.autoprefix = () => ({
143 | loader: "postcss-loader",
144 | options: {
145 | postcssOptions: {
146 | plugins: [require("autoprefixer")()],
147 | },
148 | },
149 | });
150 |
151 | exports.loadImages = ({ limit } = {}) => ({
152 | module: {
153 | rules: [
154 | {
155 | test: /\.(png|jpg)$/,
156 | type: "asset",
157 | parser: {
158 | dataUrlCondition: {
159 | maxSize: limit,
160 | },
161 | },
162 | },
163 | ],
164 | },
165 | });
166 |
167 | exports.generateSourceMaps = ({ type }) => ({
168 | devtool: type,
169 | });
170 |
--------------------------------------------------------------------------------
/webpack.ssr.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const APP_SOURCE = path.join(__dirname, "src");
4 |
5 | module.exports = {
6 | mode: "production",
7 | entry: { index: path.join(APP_SOURCE, "ssr.js") },
8 | output: {
9 | path: path.join(__dirname, "static"),
10 | filename: "[name].js",
11 | libraryTarget: "umd",
12 | globalObject: "this",
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | include: APP_SOURCE,
19 | use: "babel-loader",
20 | },
21 | ],
22 | },
23 | };
24 |
--------------------------------------------------------------------------------