Repository: survivejs-demos/webpack-demo
Branch: dev
Commit: 61393b456198
Files: 40
Total size: 19.0 KB
Directory structure:
gitextract_d5gfwb1v/
├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── demo.txt
├── loaders/
│ ├── demo-loader.js
│ └── pitch-loader.js
├── messages/
│ ├── en.json
│ └── fi.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
│ ├── 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
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules": false
}
]
]
}
================================================
FILE: .browserslistrc
================================================
> 1% # Browser usage over 1%
Last 2 versions # Or last two versions
IE 8 # Or IE 8
================================================
FILE: .editorconfig
================================================
root = true
# General settings for whole project
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# Format specific overrides
[*.js]
indent_style = space
indent_size = 2
================================================
FILE: .gitignore
================================================
node_modules/
dist/
i18n-build/
static/
records.json
stats.json
================================================
FILE: .prettierrc
================================================
{}
================================================
FILE: LICENSE
================================================
Copyright (c) 2016 Juho Vepsäläinen
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# webpack-demo - Webpack demo for "SurviveJS - Webpack 5"
See [SurviveJS - Webpack 5](http://survivejs.com/webpack/introduction/) to learn how/why this configuration works.
## License
MIT.
================================================
FILE: demo.txt
================================================
{}
================================================
FILE: loaders/demo-loader.js
================================================
const loaderUtils = require("loader-utils");
module.exports = function (content) {
const { name } = this.getOptions();
const url = loaderUtils.interpolateName(this, name, { content });
this.emitFile(url, content);
const path = `__webpack_public_path__ + ${JSON.stringify(url)};`;
return `export default ${path}`;
};
================================================
FILE: loaders/pitch-loader.js
================================================
module.exports = function (input) {
return input + this.getOptions().text;
};
module.exports.pitch = function (remaining, preceding, input) {
console.log(`Remaining: ${remaining}, preceding: ${preceding}
Input: ${JSON.stringify(input, null, 2)}
`);
return "pitched";
};
================================================
FILE: messages/en.json
================================================
{
"hello": "Hello world"
}
================================================
FILE: messages/fi.json
================================================
{
"hello": "Terve maailma"
}
================================================
FILE: package.json
================================================
{
"name": "webpack-demo",
"version": "3.0.15",
"description": "",
"main": "index.js",
"scripts": {
"deploy": "gh-pages -d dist",
"build:i18n": "wp --config webpack.i18n.js",
"build:ssr": "wp --config webpack.ssr.js",
"build:multi": "wp --config webpack.multi.js",
"build:stats": "wp --mode production --json > stats.json",
"build:mf": "wp --config webpack.mf.js --mode production",
"start:mf": "wp --config webpack.mf.js --mode development",
"start": "wp --mode development",
"build": "wp --mode production"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@tailwindcss/postcss": "^4.1.11",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"browser-refresh": "^1.7.3",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"express": "^5.1.0",
"gh-pages": "^6.3.0",
"git-revision-webpack-plugin": "^5.0.0",
"glob": "^11.0.3",
"loader-runner": "^4.3.0",
"loader-utils": "^3.3.1",
"memfs": "^4.17.2",
"mini-css-extract-plugin": "^2.9.2",
"mini-html-webpack-plugin": "^3.1.3",
"postcss-loader": "^8.1.1",
"prettier": "^3.6.2",
"purgecss-webpack-plugin": "^7.0.2",
"tailwindcss": "^4.1.11",
"terser-webpack-plugin": "^5.3.14",
"webpack": "^5.99.9",
"webpack-merge": "^6.0.1",
"webpack-nano": "^1.1.1",
"webpack-plugin-serve": "^1.6.0"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
}
}
================================================
FILE: plugins/demo-plugin.js
================================================
const { sources, Compilation } = require("webpack");
module.exports = class DemoPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
const pluginName = "DemoPlugin";
const { name } = this.options;
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
compilation.hooks.processAssets.tap(
{
name: pluginName,
// See lib/Compilation.js in webpack to understand different stages
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
() => compilation.emitAsset(name, new sources.RawSource("hello", true))
);
});
}
};
================================================
FILE: plugins/test-entry.js
================================================
console.log("hello from entry");
================================================
FILE: plugins/test.js
================================================
const webpack = require("webpack");
const { createFsFromVolume, Volume } = require("memfs");
// The compiler helper accepts filenames should be in the output
// so it's possible to assert the output easily.
function compile(config, filenames = []) {
return new Promise((resolve, reject) => {
const compiler = webpack(config);
compiler.outputFileSystem = createFsFromVolume(new Volume());
const memfs = compiler.outputFileSystem;
compiler.run((err, stats) => {
if (err) {
return reject(err);
}
// Now only errors are captured from stats.
// It's possible to capture more to assert.
if (stats.hasErrors()) {
return reject(stats.toString("errors-only"));
}
const ret = {};
filenames.forEach((filename) => {
// The assumption is that webpack outputs behind ./dist.
ret[filename] = memfs.readFileSync(`./dist/${filename}`, {
encoding: "utf-8",
});
});
return resolve(ret);
});
});
}
async function test() {
console.log(
await compile({
entry: "./test-entry.js",
}),
["demo"]
);
}
test();
================================================
FILE: run-loader.js
================================================
const fs = require("fs");
const path = require("path");
const { runLoaders } = require("loader-runner");
runLoaders(
{
resource: "./demo.txt",
loaders: [
{
loader: path.resolve(__dirname, "./loaders/demo-loader"),
options: {
name: "demo.[ext]",
},
},
path.resolve(__dirname, "./loaders/pitch-loader"),
],
context: {
emitFile: () => {},
},
readResource: fs.readFile.bind(fs),
},
(err, result) => (err ? console.error(err) : console.log(result))
);
================================================
FILE: server.js
================================================
const express = require("express");
const { renderToString } = require("react-dom/server");
const SSR = require("./static");
server(parseInt(process.env.PORT, 10) || 8080);
function server(port) {
const app = express();
app.use(express.static("static"));
app.get("/", (req, res) =>
res.status(200).send(renderMarkup(renderToString(SSR)))
);
app.listen(port, () => process.send && process.send("online"));
}
function renderMarkup(html) {
return `
Webpack SSR Demo
${html}
`;
}
================================================
FILE: src/bootstrap.js
================================================
import("./mf");
================================================
FILE: src/component.js
================================================
import "!demo-loader?name=foo!./main.css";
export default (text = HELLO) => {
const element = document.createElement("h1");
const worker = new Worker(new URL("./worker.js", import.meta.url));
const state = { text };
worker.addEventListener("message", ({ data: { text } }) => {
state.text = text;
element.innerHTML = text;
});
element.innerHTML = state.text;
element.onclick = () => worker.postMessage({ text: state.text });
return element;
};
================================================
FILE: src/header.js
================================================
import React from "react";
const Header = () => Demo
;
export default Header;
================================================
FILE: src/i18n.js
================================================
import "regenerator-runtime/runtime";
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [language, setLanguage] = useState("en");
const [hello, setHello] = useState("");
const changeLanguage = () => setLanguage(language === "en" ? "fi" : "en");
useEffect(() => {
translate(language, "hello").then(setHello).catch(console.error);
}, [language]);
return (
);
};
function translate(locale, text) {
return getLocaleData(locale).then((messages) => messages[text]);
}
async function getLocaleData(locale) {
return import(`../messages/${locale}.json`);
}
const root = document.createElement("div");
root.setAttribute("id", "app");
document.body.appendChild(root);
ReactDOM.render(, root);
================================================
FILE: src/index.js
================================================
import "react";
import "react-dom";
import "./main.css";
import component from "./component";
import { bake } from "./shake";
bake();
document.body.appendChild(component());
================================================
FILE: src/lazy.js
================================================
export default "Hello from lazy";
================================================
FILE: src/main.css
================================================
@tailwind base;
@tailwind components;
/* Write your utility classes here */
@tailwind utilities;
body {
background: cornsilk;
background-image: url("./logo.png");
background-repeat: no-repeat;
background-position: center;
}
================================================
FILE: src/mf.js
================================================
import ReactDOM from "react-dom";
import React from "react";
import "./main.css";
import Header from "mf/header";
function App() {
const options = ["Hello world", "Hello fed", "Hello webpack"];
const [content, setContent] = React.useState("Changes on click.");
return (
{content}
);
}
const container = document.createElement("div");
document.body.appendChild(container);
ReactDOM.render(, container);
================================================
FILE: src/multi.js
================================================
const element = document.createElement("div");
element.innerHTML = "hello multi";
document.body.appendChild(element);
================================================
FILE: src/shake.js
================================================
const shake = () => console.log("shake");
const bake = () => console.log("bake");
export { shake, bake };
================================================
FILE: src/ssr.js
================================================
const React = require("react");
const ReactDOM = require("react-dom");
const SSR = alert("hello")}>Hello world
;
// Render only in the browser, export otherwise
if (typeof document === "undefined") {
module.exports = SSR;
} else {
ReactDOM.hydrate(SSR, document.getElementById("app"));
}
================================================
FILE: src/worker.js
================================================
self.onmessage = ({ data: { text } }) => {
self.postMessage({ text: text + text });
};
================================================
FILE: tailwind.config.js
================================================
module.exports = {
content: ["./src/**/*.js"],
theme: {
extend: {},
},
plugins: [],
};
================================================
FILE: tests/add.js
================================================
module.exports = (a, b) => a + b;
================================================
FILE: tests/add.test.js
================================================
const assert = require("assert");
const add = require("./add");
describe("Demo", () => {
it("should add correctly", () => {
assert.equal(add(1, 1), 2);
});
});
================================================
FILE: tests/index.js
================================================
// Skip execution in Node
if (module.hot) {
const context = require.context(
"mocha-loader!./", // Process through mocha-loader
false, // Skip recursive processing
/\.test.js$/ // Pick only files ending with .test.js
);
// Execute each test suite
context.keys().forEach(context);
}
================================================
FILE: webpack.config.js
================================================
const { mode } = require("webpack-nano/argv");
const { merge } = require("webpack-merge");
const path = require("path");
const parts = require("./webpack.parts");
const cssLoaders = [parts.autoprefix(), parts.tailwind()];
const commonConfig = merge([
{
resolveLoader: {
alias: {
"demo-loader": path.resolve(__dirname, "loaders/demo-loader.js"),
},
},
},
{
output: {
// Tweak this to match your GitHub project name
publicPath: "/",
},
},
{ entry: ["./src"] },
parts.page({ title: "Demo" }),
parts.clean(),
parts.extractCSS({ loaders: cssLoaders }),
parts.loadImages({
limit: 15000,
}),
parts.loadJavaScript(),
parts.setFreeVariable("HELLO", "hello from config"),
]);
const productionConfig = merge([
{
output: {
chunkFilename: "[name].[contenthash].js",
filename: "[name].[contenthash].js",
assetModuleFilename: "[name].[contenthash][ext][query]",
},
recordsPath: path.join(__dirname, "records.json"),
},
parts.minifyJavaScript(),
parts.minifyCSS({
options: {
preset: ["default"],
},
}),
parts.eliminateUnusedCSS(),
parts.generateSourceMaps({ type: "source-map" }),
{
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: "runtime",
},
},
},
parts.attachRevision(),
]);
const developmentConfig = merge([parts.devServer()]);
const getConfig = (mode) => {
switch (mode) {
case "production":
return merge(commonConfig, productionConfig, { mode });
case "development":
return merge(commonConfig, developmentConfig, { mode });
default:
throw new Error(`Trying to use an unknown mode, ${mode}`);
}
};
module.exports = getConfig(mode);
================================================
FILE: webpack.i18n.js
================================================
const path = require("path");
const { MiniHtmlWebpackPlugin } = require("mini-html-webpack-plugin");
const APP_SOURCE = path.join(__dirname, "src");
module.exports = {
mode: "production",
entry: { index: path.join(APP_SOURCE, "i18n.js") },
module: {
rules: [
{
test: /\.js$/,
include: APP_SOURCE,
use: "babel-loader",
},
],
},
plugins: [new MiniHtmlWebpackPlugin()],
};
================================================
FILE: webpack.mf.js
================================================
const path = require("path");
const { component, mode } = require("webpack-nano/argv");
const { merge } = require("webpack-merge");
const parts = require("./webpack.parts");
const commonConfig = merge([
{
output: { publicPath: "/" },
},
parts.loadJavaScript(),
parts.loadImages(),
parts.page(),
parts.extractCSS({ loaders: [parts.tailwind()] }),
]);
const configs = {
development: merge(
{ entry: ["webpack-plugin-serve/client"] },
parts.devServer()
),
production: {},
};
const shared = {
react: { singleton: true },
"react-dom": { singleton: true },
};
const componentConfigs = {
app: merge(
{
entry: [path.join(__dirname, "src", "bootstrap.js")],
},
parts.page(),
parts.federateModule({
name: "app",
remotes: { mf: "mf@/mf.js" },
shared,
})
),
header: merge(
{
entry: [path.join(__dirname, "src", "header.js")],
},
parts.federateModule({
name: "mf",
filename: "mf.js",
exposes: { "./header": "./src/header" },
shared,
})
),
};
if (!component) throw new Error("Missing component name");
module.exports = merge(
commonConfig,
configs[mode],
{ mode },
componentConfigs[component]
);
================================================
FILE: webpack.multi.js
================================================
const { merge } = require("webpack-merge");
const parts = require("./webpack.parts");
module.exports = merge(
{ mode: "production", entry: { app: "./src/multi.js" } },
parts.page({ title: "Demo" }),
parts.page({ title: "Another", url: "another" })
);
================================================
FILE: webpack.parts.js
================================================
const { WebpackPluginServe } = require("webpack-plugin-serve");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");
const glob = require("glob");
const { PurgeCSSPlugin } = require("purgecss-webpack-plugin");
const webpack = require("webpack");
const { GitRevisionPlugin } = require("git-revision-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const { MiniHtmlWebpackPlugin } = require("mini-html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
const ALL_FILES = glob.sync(path.join(__dirname, "src/*.js"));
const APP_SOURCE = path.join(__dirname, "src");
exports.federateModule = ({ name, filename, exposes, remotes, shared }) => ({
plugins: [
new ModuleFederationPlugin({ name, filename, exposes, remotes, shared }),
],
});
exports.page = ({ path = "", template, title, chunks } = {}) => ({
plugins: [
new MiniHtmlWebpackPlugin({
chunks,
filename: `${path && path + "/"}index.html`,
context: { title },
template,
}),
],
});
exports.setFreeVariable = (key, value) => {
const env = {};
env[key] = JSON.stringify(value);
return {
plugins: [new webpack.DefinePlugin(env)],
};
};
exports.minifyCSS = ({ options }) => ({
optimization: {
minimizer: [new CssMinimizerPlugin({ minimizerOptions: options })],
},
});
exports.minifyJavaScript = () => ({
optimization: {
minimizer: [new TerserPlugin()],
},
});
exports.attachRevision = () => ({
plugins: [
new webpack.BannerPlugin({
banner: new GitRevisionPlugin().version(),
}),
],
});
exports.clean = () => ({
output: {
clean: true,
},
});
exports.loadJavaScript = () => ({
module: {
rules: [
{
test: /\.js$/,
include: APP_SOURCE, // Consider extracting as a parameter
use: "babel-loader",
},
],
},
});
exports.eliminateUnusedCSS = () => ({
plugins: [
new PurgeCSSPlugin({
paths: ALL_FILES, // Consider extracting as a parameter
extractors: [
{
extractor: (content) =>
content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [],
extensions: ["html"],
},
],
}),
],
});
exports.extractCSS = ({ options = {}, loaders = [] } = {}) => {
return {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: MiniCssExtractPlugin.loader, options },
"css-loader",
].concat(loaders),
// If you distribute your code as a package and want to
// use _Tree Shaking_, then you should mark CSS extraction
// to emit side effects. For most use cases, you don't
// have to worry about setting flag.
sideEffects: true,
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
}),
],
};
};
exports.devServer = () => ({
watch: true,
plugins: [
new WebpackPluginServe({
port: parseInt(process.env.PORT, 10) || 8080,
static: "./dist", // Expose if output.path changes
liveReload: true,
waitForBuild: true,
}),
],
});
exports.tailwind = () => ({
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [require("@tailwindcss/postcss")()],
},
},
});
exports.autoprefix = () => ({
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [require("autoprefixer")()],
},
},
});
exports.loadImages = ({ limit } = {}) => ({
module: {
rules: [
{
test: /\.(png|jpg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: limit,
},
},
},
],
},
});
exports.generateSourceMaps = ({ type }) => ({
devtool: type,
});
================================================
FILE: webpack.ssr.js
================================================
const path = require("path");
const APP_SOURCE = path.join(__dirname, "src");
module.exports = {
mode: "production",
entry: { index: path.join(APP_SOURCE, "ssr.js") },
output: {
path: path.join(__dirname, "static"),
filename: "[name].js",
libraryTarget: "umd",
globalObject: "this",
},
module: {
rules: [
{
test: /\.js$/,
include: APP_SOURCE,
use: "babel-loader",
},
],
},
};