mkdocs-material/webpack.config.ts

364 lines
11 KiB
TypeScript
Raw Normal View History

2019-09-29 01:30:56 +03:00
/*
2020-02-10 20:32:28 +03:00
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
2019-09-29 01:30:56 +03:00
*
* 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 NON-INFRINGEMENT. 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.
*/
// tslint:disable no-var-requires
2020-02-10 20:32:28 +03:00
import * as CopyPlugin from "copy-webpack-plugin"
import * as EventHooksPlugin from "event-hooks-webpack-plugin"
import * as fs from "fs"
import { minify as minhtml } from "html-minifier"
2019-09-29 01:30:56 +03:00
import * as path from "path"
2020-02-10 20:32:28 +03:00
import { toPairs } from "ramda"
import { minify as minjs } from "terser"
2019-12-18 18:38:36 +03:00
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin"
2020-02-19 17:45:52 +03:00
import { Configuration } from "webpack"
2020-02-10 20:32:28 +03:00
import * as AssetsManifestPlugin from "webpack-assets-manifest"
2019-09-29 01:30:56 +03:00
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Material icons
*/
const data = require("material-design-icons-svg/paths")
const icon = require("material-design-icons-svg")(data)
2019-09-29 01:30:56 +03:00
/* ----------------------------------------------------------------------------
* Helper functions
2019-09-29 01:30:56 +03:00
* ------------------------------------------------------------------------- */
/**
* Webpack base configuration
2019-09-29 01:30:56 +03:00
*
* @param args - Command-line arguments
*
* @return Webpack configuration
*/
function config(args: Configuration): Configuration {
2020-02-10 20:32:28 +03:00
const assets = {}
2019-09-29 01:30:56 +03:00
return {
mode: args.mode,
/* Loaders */
module: {
rules: [
/* TypeScript */
{
test: /\.tsx?$/,
use: [
{
loader: "ts-loader",
options: {
2020-02-10 20:32:28 +03:00
experimentalWatchApi: true,
2019-09-29 01:30:56 +03:00
transpileOnly: true,
compilerOptions: {
importHelpers: true,
module: "esnext"
2019-09-29 01:30:56 +03:00
}
}
}
],
exclude: /\/node_modules\//
},
2020-02-10 20:32:28 +03:00
/* SASS stylesheets */
{
test: /\.scss$/,
use: [
{
loader: "file-loader",
options: {
name: `[name]${
args.mode === "production" ? ".[md5:hash:hex:8].min" : ""
}.css`,
outputPath: "assets/stylesheets",
publicPath: path.resolve(__dirname, "material")
}
},
"extract-loader",
{
loader: "css-loader",
options: {
2020-02-11 13:02:46 +03:00
url: false,
2020-02-10 20:32:28 +03:00
sourceMap: args.mode !== "production"
}
},
2020-03-18 13:00:43 +03:00
{
loader: "string-replace-loader",
options: {
multiple: [
{
search: "\\{{2}\\s+?([^}]+)\\s+?\\}{2}",
replace(_: string, props: string) {
const [name, color] = props.split(" ")
/* Load icon and set color, if given */
const svg = icon.getSVG(
path.basename(name, ".json"),
color ? ` style="fill: ${color}"` : undefined
)
.replace(/"/g, "'")
.replace(/#/g, "%23")
/* Return encoded icon */
return `data:image/svg+xml;utf8,${svg}`
2020-03-18 13:00:43 +03:00
},
flags: "g"
}
]
}
},
2020-02-10 20:32:28 +03:00
{
loader: "postcss-loader",
options: {
ident: "postcss",
plugins: () => [
require("autoprefixer")(),
2020-03-18 13:00:43 +03:00
require("css-mqpacker")
2020-02-10 20:32:28 +03:00
],
sourceMap: args.mode !== "production"
}
},
{
loader: "sass-loader",
options: {
implementation: require("sass"),
sassOptions: {
includePaths: [
"node_modules/modularscale-sass/stylesheets",
"node_modules/material-design-color",
"node_modules/material-shadows"
]
},
sourceMap: args.mode !== "production"
}
}
]
2019-09-29 01:30:56 +03:00
}
]
},
/* Module resolver */
resolve: {
modules: [
__dirname,
path.resolve(__dirname, "node_modules")
],
2019-12-18 18:38:36 +03:00
extensions: [".ts", ".tsx", ".js", ".json"],
plugins: [
new TsconfigPathsPlugin()
]
2019-09-29 01:30:56 +03:00
},
2020-02-10 20:32:28 +03:00
/* Plugins */
plugins: [
new AssetsManifestPlugin({
output: "assets/manifest.json",
assets,
customize({ key, value }) {
return {
key: key.replace(/\.scss$/, ".css"),
value
}
}
2020-02-10 20:32:28 +03:00
})
],
2019-12-17 12:01:46 +03:00
/* Source maps */
2019-12-22 18:52:28 +03:00
devtool: "source-map",
2020-02-11 19:14:28 +03:00
/* Filter false positives and omit verbosity */
2019-12-22 18:52:28 +03:00
stats: {
2020-02-11 19:14:28 +03:00
entrypoints: false,
excludeAssets: [
/\.(icons)/,
/\/(images|lunr)\//,
/\.(html|py|yml)$/
],
2019-12-22 18:52:28 +03:00
warningsFilter: [
/export '.[^']+' was not found in/
2019-12-22 18:52:28 +03:00
]
}
2019-09-29 01:30:56 +03:00
}
}
/* ----------------------------------------------------------------------------
* Configuration
* ------------------------------------------------------------------------- */
/**
* Webpack configuration
*
* @param env - Webpack environment arguments
* @param args - Command-line arguments
*
2019-12-22 18:52:28 +03:00
* @return Webpack configurations
*/
2020-02-10 20:32:28 +03:00
export default (_env: never, args: Configuration): Configuration[] => {
const hash = args.mode === "production" ? ".[chunkhash].min" : ""
const base = config(args)
return [
/* Application */
{
...base,
entry: {
"assets/javascripts/bundle": "src/assets/javascripts"
},
output: {
path: path.resolve(__dirname, "material"),
filename: `[name]${hash}.js`,
hashDigestLength: 8,
libraryTarget: "window"
},
/* Plugins */
2020-02-10 20:32:28 +03:00
plugins: [
...base.plugins,
/* FontAwesome icons */
2020-02-10 20:32:28 +03:00
new CopyPlugin([
{ to: ".icons/fontawesome", from: "**/*.svg" },
{ to: ".icons/fontawesome", from: "../LICENSE.txt" }
2020-02-10 20:32:28 +03:00
], {
context: "node_modules/@fortawesome/fontawesome-free/svgs"
}),
/* Material icons */
new CopyPlugin([
{
to: ".icons/material/[name].svg",
from: "**/*.json",
toType: "template",
transform: (_, file) => icon.getSVG(path.basename(file, ".json"))
}
], {
context: "node_modules/material-design-icons-svg/paths"
}),
/* GitHub octicons */
new CopyPlugin([
{ to: ".icons/octicons", from: "*.svg" },
{ to: ".icons/octicons", from: "../../LICENSE" }
], {
context: "node_modules/@primer/octicons/build/svg"
}),
/* Search stemmers and segmenters */
2020-02-10 20:32:28 +03:00
new CopyPlugin([
{ to: "assets/javascripts/lunr", from: "min/*.js" },
{
to: "assets/javascripts/lunr/tinyseg.min.js",
from: "tinyseg.js",
transform: content => minjs(`${content}`).code!
}
2020-02-10 20:32:28 +03:00
], {
context: "node_modules/lunr-languages"
}),
/* Template files */
2020-02-10 20:32:28 +03:00
new CopyPlugin([
{ from: ".icons/*.svg" },
{ from: "assets/images/*" },
2020-02-10 20:32:28 +03:00
{ from: "**/*.{py,yml}" },
{
from: "**/*.html",
transform: content => {
const metadata = require("./package.json")
const banner =
"{#-\n" +
" This file was automatically generated - do not edit\n" +
"-#}\n"
return banner + minhtml(content.toString(), {
collapseBooleanAttributes: true,
includeAutoGeneratedTags: false,
minifyCSS: true,
minifyJS: true,
removeComments: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true
})
/* Remove empty lines without collapsing everything */
.replace(/^\s*[\r\n]/gm, "")
/* Write theme version into template */
.replace("$md-name$", metadata.name)
.replace("$md-version$", metadata.version)
}
}
], {
context: "src"
}),
/* Hooks */
2020-02-10 20:32:28 +03:00
new EventHooksPlugin({
afterEmit: () => {
/* Replace asset URLs in base template */
if (args.mode === "production") {
const manifest = require("./material/assets/manifest.json")
const template = toPairs<string>(manifest)
.reduce((content, [from, to]) => {
return content.replace(from, to)
}, fs.readFileSync("material/base.html", "utf8"))
/* Save template with replaced assets */
fs.writeFileSync("material/base.html", template, "utf8")
}
2020-02-10 20:32:28 +03:00
}
})
],
/* Optimizations */
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /\/node_modules\//,
name: "assets/javascripts/vendor",
chunks: "all"
}
}
}
}
2020-02-10 20:32:28 +03:00
},
/* Search worker */
{
...base,
entry: {
"assets/javascripts/worker/search":
2020-03-27 17:29:17 +03:00
"src/assets/javascripts/integrations/search/worker/main"
2020-02-10 20:32:28 +03:00
},
output: {
path: path.resolve(__dirname, "material"),
filename: `[name]${hash}.js`,
hashDigestLength: 8,
libraryTarget: "var"
2020-02-19 17:45:52 +03:00
}
}
2020-02-10 20:32:28 +03:00
]
}