@appnest/web-config
Version:
A Rollup configuration to help you build modern web applications.
1,450 lines (1,295 loc) • 36.9 kB
JavaScript
import ts from '@wessberg/rollup-plugin-ts';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import precss from 'precss';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import license from 'rollup-plugin-license';
import resolve$1 from '@rollup/plugin-node-resolve';
import progress from 'rollup-plugin-progress';
import serve from 'rollup-plugin-serve';
import { terser } from 'rollup-plugin-terser';
import visualizer from 'rollup-plugin-visualizer';
import boxen from 'boxen';
import colors, { green, yellow } from 'colors';
import fileSize from 'filesize';
import fse, { createWriteStream, readFileSync, existsSync, emptyDirSync, appendFile, copy as copy$1 } from 'fs-extra';
import gzipSize from 'gzip-size';
import readdir from 'recursive-readdir-sync';
import { normalize, resolve, dirname, parse, join } from 'path';
import brotli from 'brotli';
import { createFilter } from '@rollup/pluginutils';
import { compress as compress$1 } from 'targz';
import { readFile } from 'fs';
import MagicString from 'magic-string';
import postcss from 'postcss';
import sass from 'node-sass';
import { createServer } from 'livereload';
import { generate } from 'escodegen';
import { parseModule, parseScript } from 'esprima';
import { replace as replace$1 } from 'estraverse';
import { minify } from 'html-minifier';
import { injectManifest, generateSW } from 'workbox-build';
const defaultConfig$9 = {
sizes: {},
render: defaultRender,
fileName: "budget.txt",
silent: true,
threshold: 0,
timeout: 2000
};
/**
* Returns the gzipped bundle size.
* @param content
* @returns {Number}
*/
function getGzippedSizeBytes(content) {
return gzipSize.sync(content);
}
/**
* Returns the bundle size.
* @param content
* @returns {number}
*/
function getSizeBytes(content) {
return Buffer.byteLength(content);
}
/**
* Clamps a value between a max and a min (inclusive).
* @param value
* @param min
* @param max
* @returns {number}
*/
function clamp(value, min, max) {
return Math.max(Math.min(value, max), min);
}
/**
* Rounds a number to two digits.
* @param num
* @returns {number}
*/
function roundNumber(num) {
return Math.round(num * 100) / 100;
}
/**
* Initializes an array with a length and a start value.
* @param length
* @param value
*/
function initArray(length, value) {
return Array(length).fill(value);
}
/**
* Returns the file name for a path.
* @param path
*/
function fileNameForPath(path) {
return path.replace(/^.*[\\\/]/, "");
}
/**
* Returns a formatted string containing the status of the file budget.
* @param gzippedSize
* @param max
* @param sizePerc
* @param aboveMax
* @param name
* @param format
*/
function defaultRender({
gzippedSize,
max,
sizePerc,
aboveMax,
name,
formatted
}) {
// Create methods for formatting the colors
const titleColor = formatted ? colors["green"].bold : text => text;
const valueColor = formatted ? colors["yellow"] : text => text;
const statusColor = formatted ? colors[aboveMax ? "red" : "yellow"] : text => text;
const barMaxLength = 20;
const values = [`${titleColor("File Name:")} ${valueColor(name)}`, `${titleColor("Budget Size:")} ${valueColor(fileSize(max))}`, // `${titleColor("Actual Size:")} ${statusColor(fileSize(actualSize))}`,
`${titleColor("Gzipped Size:")} ${statusColor(fileSize(gzippedSize))}`, `${statusColor("[")}${statusColor(initArray(Math.round(clamp(sizePerc * barMaxLength, 0, barMaxLength)), "#").join(""))}${statusColor(initArray(clamp(Math.round((1 - sizePerc) * barMaxLength), 0, barMaxLength), ".").join(""))}${statusColor("]")} ${statusColor("(" + roundNumber(sizePerc * 100) + "%)")}`];
return boxen(values.join("\n"), {
padding: 1
});
}
/**
* Returns the budget for a specific path.
* If no budget has been specified null is returned.
* @param path
* @param sizes
* @returns {*}
*/
function budgetForPath(path, sizes) {
for (const [name, max] of Object.entries(sizes)) {
const isExtension = name.startsWith(".");
if (path.match(name + (isExtension ? "$" : ""))) {
return max;
}
}
return null;
}
/**
* A Rollup plugin that compares the sizes of the files to a specified budget.
* @param config
* @returns {{name: string, generateBundle(*, *, *): (undefined|void)}}
*/
function budget(config = {}) {
const {
sizes,
timeout,
render,
silent,
fileName,
threshold
} = { ...defaultConfig$9,
...config
};
const isOutputJson = fileName.endsWith(".json");
return {
name: "budget",
generateBundle: async (outputOptions, bundle, isWrite) => {
// If no sizes has been specifies we can already abort now.
if (Object.keys(sizes).length === 0) {
return;
} // Wait a small amount of time for the bundle to finish before analyzing the bundle.
setTimeout(() => {
const target = outputOptions.dir;
const stream = createWriteStream(`${outputOptions.dir}/${fileName}`);
const results = readdir(target).map(path => {
return {
max: budgetForPath(path, sizes),
path
};
}).filter(({
max
}) => max != null && max > 0).map(({
path,
max
}) => {
const content = readFileSync(path);
const name = fileNameForPath(path);
const gzippedSize = getGzippedSizeBytes(content);
const sizePerc = gzippedSize / max;
const aboveMax = sizePerc > 1;
return {
name,
gzippedSize,
sizePerc,
aboveMax,
max,
path
};
}) // Ensure the ones closest to the budget are in top
.sort((a, b) => b.sizePerc - a.sizePerc); // Go through all results and report them
for (const result of results) {
// Skip the reporting if the size perc is below the threshold
if (result.sizePerc < threshold) {
return;
} // Print to the console if not silent
if (!silent) {
console.log(render({ ...result,
formatted: true
}));
} // Write to the file
if (!isOutputJson) {
stream.write(render(result) + "\n\n");
}
} // Write the output as json instead
if (isOutputJson) {
stream.write(JSON.stringify(results, null, 2));
}
stream.end();
}, timeout);
}
};
}
const defaultConfig$8 = {
targets: [],
verbose: true
};
/**
* A Rollup plugin that clean directories before rebuilding.
* @param config
*/
function clean(config = {}) {
const {
targets,
verbose
} = { ...defaultConfig$8,
...config
};
return {
name: "clean",
generateBundle: () => {
for (const target of targets) {
const path = normalize(target);
if (existsSync(path)) {
if (verbose) {
console.log(green(`[clean] - Cleaning "${path}"`));
}
emptyDirSync(path);
}
}
}
};
}
const defaultConfig$7 = {
verbose: true,
include: [],
exclude: [],
compressors: [compressGzip],
timeout: 2000
};
/**
* Compresses a file using gzip.
* @param src
* @param verbose
*/
function compressGzip({
src,
verbose
}) {
const dest = `${src}.gz`;
compress$1({
src,
dest
}, err => {
if (verbose && err != null) {
console.log(yellow(`[gzip] - Could not compress "${src}" to "${dest}"\n`), err);
}
});
}
/**
* Compresses a file using brotli.
* @param src
* @param verbose
*/
function compressBrotli({
src,
verbose
}) {
const buffer = brotli.compress(readFileSync(src));
const dest = `${src}.br`;
appendFile(dest, buffer, err => {
if (verbose && err != null) {
console.log(yellow(`[brotli] - Could not compress "${src}" to "${dest}"\n`), err);
}
});
}
/**
* A Rollup plugin that compresses the files in the bundle after building.
* @param config
* @returns {{name: string, generateBundle: generateBundle}}
*/
function compress(config = {}) {
const {
verbose,
dir,
timeout,
compressors,
include,
exclude
} = { ...defaultConfig$7,
...config
};
const filter = createFilter(include, exclude);
return {
name: "compress",
generateBundle: (outputOptions, bundle, isWrite) => {
if (!isWrite) return; // Start the timeout to make sure the rollup bundle and all of the files
// will be in the target folder when we are compressing.
setTimeout(() => {
// Grab the files from the build folder
const target = dir || outputOptions.dir;
const files = readdir(target).filter(path => !path.endsWith(".gz") && filter(path)); // Compress all files
for (const src of files) {
for (const compress of compressors) {
compress({
src,
verbose
});
}
} // Tell the user that everything went fine
if (verbose) {
console.log(green(`[compress] - Successfully compressed ${files.length} files`));
}
}, timeout);
}
};
}
/**
* Default configuration for the copy plugin.
* @type {{resources: Array}}
*/
const defaultConfig$6 = {
resources: [],
verbose: true,
overwrite: true
};
/**
* A Rollup plugin that copies resources from one location to another.
* @param config
*/
function copy(config = {}) {
const {
resources,
verbose,
overwrite
} = { ...defaultConfig$6,
...config
};
return {
name: "copy",
generateBundle: async (outputOptions, bundle, isWrite) => {
if (!isWrite) return;
for (const [from, to] of resources) {
try {
if (overwrite || !existsSync(to)) {
await copy$1(from, to);
}
} catch (err) {
if (verbose) {
console.log(yellow(`[copy] - The file "${from}" could not be copied to "${to}"\n`), err.message);
}
}
}
}
};
}
/**
* Default configuration for the polyfill.
*/
const defaultPolyfillConfig = {
src: "https://polyfill.app/api/polyfill",
crossorigin: true,
force: false,
context: "window",
features: [],
options: []
};
/**
* Default configuration for the html template plugin.
* Note that both template and target are required.
*/
const defaultConfig$5 = {
transform: transformTemplate,
transformScript: transformScript,
verbose: true,
include: [],
exclude: [],
scriptType: "module",
polyfillConfig: defaultPolyfillConfig
};
/**
* Returns the script tag for the polyfill config.
* @param crossorigin
* @param features
* @param src
* @param options
*/
function getPolyfillScript({
crossorigin,
features,
src,
options
}) {
src = `${src}?${features.length > 0 ? `features=${features.join(",")}` : ""}${options.length > 0 ? `|${options.join("|")}` : ""}`;
return `<script ${crossorigin ? "crossorigin" : ""} src="${src}"></script>`;
}
/**
* Transform the script tag.
* @param filename
* @param scriptType
*/
function transformScript({
filename,
scriptType
}) {
return `<script src="${filename}" type="${scriptType}"></script>`;
}
/**
* Transform the template and inserts a script tag for each file.
* Injects the script tags before the body close tag.
* @param template
* @param bodyCloseTagIndex
* @param fileNames
* @param scriptType
* @param polyfillConfig
* @param transformScript
*/
function transformTemplate({
template,
bodyCloseTagIndex,
fileNames,
scriptType,
polyfillConfig,
transformScript
}) {
return [template.slice(0, bodyCloseTagIndex), polyfillConfig.features.length > 0 ? `${getPolyfillScript(polyfillConfig)}\n` : "", ...fileNames.map(filename => transformScript({
filename,
scriptType
})).join("\n"), template.slice(bodyCloseTagIndex, template.length)].join("");
}
/**
* Injects the sources for the bundle entrypoints and generates a HTML file.
* Inspired by https://github.com/bengsfort/rollup-plugin-generate-html-template/blob/master/src/index.js
* @param bundle
* @param template
* @param target
* @param filter
* @param scriptType
* @param verbose
* @param include
* @param exclude
* @param transform
* @param polyfillConfig
* @param transformScript
*/
async function generateFile({
bundle,
template,
target,
filter,
scriptType,
verbose,
include,
exclude,
transform,
polyfillConfig,
transformScript
}) {
return new Promise((res, rej) => {
readFile(template, (err, buffer) => {
// If the file could not be read, abort!
if (err) {
return rej(err);
} // Convert the buffer into a string
const template = buffer.toString("utf8"); // Grab the index of the body close tag
const bodyCloseTagIndex = template.lastIndexOf("</body>"); // Grab fileNames of the entry points
const unfilteredFilenames = Object.values(bundle).map(value => value.fileName);
const fileNames = unfilteredFilenames.filter(name => filter(name)); // Error handling
if (verbose && fileNames.length === 0) {
console.log(colors.yellow(`[htmlTemplate] - No scripts were injected into the "${target}" template file. Make sure to specify the files that should be injected using the include option. Currently the include option has been set to "${include}" and the exclude option to "${exclude}". The filenames passed to the plugin are "${unfilteredFilenames.join(", ")}"\n`));
} // Transform the template
const html = transform({
template,
bodyCloseTagIndex,
fileNames,
scriptType,
transformScript,
polyfillConfig: polyfillConfig
}); // Write the injected template to a file.
try {
fse.outputFileSync(target, html);
res();
} catch (err) {
rej(err);
}
});
});
}
/**
* A Rollup plugin that injects the bundle entry points into a HTML file.
* @param config
*/
function htmlTemplate(config = {}) {
config = { ...defaultConfig$5,
...config
};
const {
template,
target,
include,
exclude,
polyfillConfig
} = { ...defaultConfig$5,
...config
};
const filter = createFilter(include, exclude); // Throw error if neither the template nor the target has been defined
if (template == null || target == null) {
throw new Error(`The htmlTemplate plugin needs both a template and a target.`);
}
return {
name: "htmlTemplate",
generateBundle: async (outputOptions, bundle, isWrite) => {
if (!isWrite) return; // @ts-ignore
return generateFile({ ...config,
polyfillConfig: { ...defaultPolyfillConfig,
...polyfillConfig
},
bundle,
filter
});
}
};
}
/**
* Default empty source map.
* @type {{version: number, file: null, sources: *[], sourcesContent: *[], names: Array, mappings: string}}
*/
const emptySourcemap = {
version: 3,
file: null,
sources: [null],
sourcesContent: [null],
names: [],
mappings: ""
};
/**
* Default configuration for the import SCSS plugin.
* @type {{plugins: Array, extensions: string[], globals: Array}}
*/
const defaultConfig$4 = {
plugins: [],
extensions: [".css", ".scss"],
globals: [],
postcssConfig: {},
sassConfig: {},
transform: transformImport
};
/**
* Default transform.
* @param id
* @param isGlobal
*/
function transformImport(id, isGlobal) {
return isGlobal ? transformGlobal : transformDefault;
}
/**
* Overwrites the css file with "export default".
* @param css
* @returns {string}
*/
function transformDefault(css) {
return `export default \`${css}\``;
}
/**
* Overwrites the css file with a global inject into the head.
* @param css
* @returns {string}
*/
function transformGlobal(css) {
return `
const css = \`${css}\`;
const $styles = document.createElement("style");
$styles.innerText = css;
document.head.appendChild($styles);
export default css;
`;
}
/**
* Processes a SCSS file by running it through a processor and generating the code and its corresponding sourcemap.
* @param data
* @param id
* @param processor
* @param overwrite
* @param postcssConfig
* @param sassConfig
*/
async function processFile$1({
data,
id,
processor,
overwrite,
postcssConfig,
sassConfig
}) {
return new Promise(res => {
// Compile the data using the sass compiler
const css = sass.renderSync({
file: resolve(id),
sourceMap: false
/* We generate sourcemaps later */
,
...(sassConfig || {})
}).css.toString(); // The magic strings cannot handle empty strings, therefore we test whether we should already abort now.
if (css.trim() === "") {
return res({
code: overwrite(""),
map: emptySourcemap
});
} // Create a magic string container to generate source map
const stringContainer = new MagicString(css); // Construct the options
const processOptions = {
from: id,
to: id,
map: {
inline: false,
annotation: false
},
...(postcssConfig || {})
}; // Process the file content
processor.process(stringContainer.toString(), processOptions).then(result => {
const css = result.css;
stringContainer.overwrite(0, stringContainer.length(), overwrite(css));
res({
code: stringContainer.toString(),
map: stringContainer.generateMap()
});
});
});
}
/**
* A Rollup plugin that makes it possible to import style files using postcss.
* Looks for the "import css from 'styles.scss'" and "import 'styles.scss'" syntax as default.
* @param config
*/
function importStyles(config = {}) {
config = { ...defaultConfig$4,
...config
};
const {
plugins,
extensions,
globals,
transform
} = config; // Determines whether the file should be handled by the plugin or not.
const filter = id => extensions.find(ext => id.endsWith(ext)) != null; // Determines whether the file is global or not
const isGlobal = id => globals.find(name => id.endsWith(name)) != null; // Create the postcss processor based on the plugins.
const processor = postcss(plugins);
return {
name: "importStyles",
resolveId: (id, importer) => {
if (!importer || !filter(id)) return;
return resolve(dirname(importer), id);
},
transform: async (data, id) => {
if (!filter(id)) return;
const overwrite = transform(id, isGlobal(id)); // @ts-ignore
return processFile$1({ ...config,
processor,
overwrite,
id,
data
});
}
};
}
/**
* #########################################
* Parts of this code is heavily inspired by https://github.com/thgh/rollup-plugin-livereload.
* The license has therefore been included.
* #########################################
*/
/**
* Default configuration for the livereload plugin.
*/
const defaultConfig$3 = {
watch: "dist",
port: 35729,
verbose: true
};
/**
* Returns the livereload html.
*/
function livereloadHtml(port) {
return `/* Inserted by the Livereload plugin */
if (typeof document !== 'undefined') {
(function(doc, id) {
/* Ensure that a script does not exist in the doc yet */
var $container = doc.head || doc.body;
if ($container == null || $container.querySelector("#" + id) != null) {
return;
}
/* Create script that takes care of reloading each time a watched file changes */
var $script = doc.createElement("script");
$script.async = true;
$script.id = id;
$script.src = "//" + (location.host || "localhost").split(":")[0] + ":${port}/livereload.js?snipver=1";
/* Inject the script as the first one */
$container.insertBefore($script, $container.firstChild);
})(document, "rollup-plugin-livereload");
}`;
}
/**
* Subscribes the server to terminate when required.
* @param server
*/
function attachTerminationListeners(server) {
// Hook up listeners that kills the server if the process is terminated for some reason
const terminationSignals = ["SIGINT", "SIGTERM", "SIGQUIT"];
for (const signal of terminationSignals) {
process.on(signal, () => killServer(server));
} // Rethrow the error
server.on("error", err => {
server.close();
throw err;
});
}
/**
* Tears down the server.
* @param server
*/
function killServer(server) {
server.close();
process.exit();
}
/**
* A Rollup plugin that live reload files as they changes.
* @param config
* @returns {*}
*/
function livereload(config = {}) {
const {
watch,
port,
verbose
} = { ...defaultConfig$3,
...config
}; // Start watching the files
const server = createServer({
watch,
port,
verbose
});
const paths = Array.isArray(watch) ? watch : [watch]; // @ts-ignore // TODO
server.watch(paths.map(p => resolve(process.cwd(), p)));
attachTerminationListeners(server);
return {
name: "livereload",
banner: () => livereloadHtml(port),
generateBundle: () => {
if (verbose) {
console.log(colors.green(`[livereload] - Enabled.`));
}
}
};
}
/**
* #########################################
* Parts of this code is heavily inspired by https://github.com/edge0701/minify-lit-html-loader.
* The license has therefore been included.
* #########################################
*/
/**
* The default configuration for the minify-lit-html plugin.
*/
const defaultConfig$2 = {
include: [/\.js$/, /\.ts$/],
exclude: [],
verbose: true,
esprima: {
loc: true,
range: true,
tolerant: true,
tokens: false
},
htmlMinifier: {
caseSensitive: true,
minifyCSS: false
/* Should be set as true, but the HTML minifier won't allow the <style>${css}</style> syntax, so I disabled it */
,
preventAttributesEscaping: true,
preserveLineBreaks: false,
collapseWhitespace: true,
conservativeCollapse: true,
removeComments: true,
ignoreCustomFragments: [/<\s/, /<=/, /\$\{/, /\}/,
/* The HTML minifier won't parse parts with double quote inside (eg. @click="${() => alert("Hello World")}") */
/"\${[^}]+"[^}]+}"/]
}
};
/**
* Creates a transformer that traverses an ast, minifying the html`...` parts from lit-html.
* This function is heavily inspired by https://github.com/edge0701/minify-lit-html-loader/blob/master/src/index.ts.
* @param code
* @param config
*/
function createTransformer({
code,
config
}) {
const chunks = code.split("");
return ast => {
return replace$1(ast, {
enter: node => {
// If the node type is a TaggedTemplateExpression we know we are looking at a TemplateResult.
if (node.type === "TaggedTemplateExpression") {
// If the tag name or property name is html we know we are looking at a html`...` part.
if (node.tag.type === "Identifier" && node.tag.name === "html" || node.tag.type === "MemberExpression" && node.tag.property.type === "Identifier" && node.tag.property.name === "html") {
// Minify the HTML inside the html tagged template literals.
const mini = minify(chunks.slice(node.quasi.range[0] + 1, node.quasi.range[1] - 1).join(""), config.htmlMinifier); // Return the new node
return { ...node,
quasi: { ...node.quasi,
quasis: [{
type: "TemplateElement",
value: {
raw: mini
},
range: [node.quasi.range[0], mini.length]
}]
}
};
}
}
},
fallback: "iteration"
});
};
}
/**
* Figures out whether the code is a script type or module type.
* @param code
* @param config
*/
function parseAst({
code,
config
}) {
try {
return parseModule(code, config.esprima);
} catch (e) {
return parseScript(code, config.esprima);
}
}
/**
* Processes the code by minifying the html using in the tagged template literals.
* @param code
* @param id
* @param config
* @returns {Promise<void>}
*/
function processFile({
code,
id,
config
}) {
return new Promise(res => {
try {
// Create transformer that traverses the ast and minifies the html`...` parts.
const transform = createTransformer({
code,
config
}); // Build an ast from the current config
const ast = parseAst({
code,
config
}); // // Create new ast using the transformer
const newAst = transform(ast); // Regenerate the code based on the new ast.
// If sourceMapWithCode is truthy, an object is returned from
// generate() of the form: { code: .. , map: .. }
const {
code: minifiedCode,
map
} = generate(newAst, {
sourceMapWithCode: true,
sourceMap: id,
sourceContent: code,
sourceCode: code
});
return res({
code: minifiedCode,
map: map.toString()
});
} catch (err) {
if (config.verbose) {
console.log(colors.yellow(`[minifyLitHTML] - Could not parse "${err.message}" in "${id}"\n`));
} // Sometimes we cannot parse the file. This should however not stop the build from finishing.
res({
code,
map: emptySourcemap
});
}
});
}
/**
* A Rollup plugin that minifies lit-html templates.
* @param config
* @returns {{name: string, resolveId: (function(*=, *=): *), transform: (function(*, *=): Promise<void>)}}
*/
function minifyLitHTML(config = {}) {
config = { ...defaultConfig$2,
...config
};
const {
include,
exclude
} = config; // Generate a filter that determines whether the file should be handled by the plugin or not.
const filter = createFilter(include, exclude);
return {
name: "minifyLitHTML",
resolveId: (id, importer) => {
if (!importer || !filter(id)) return;
return resolve(dirname(importer), id);
},
transform: (code, id) => {
if (!filter(id)) return;
return processFile({
code,
id,
config: config
});
}
};
}
const defaultConfig$1 = {
verbose: true,
resources: []
};
/**
* A Rollup plugin that replaces an import with another import.
* @param config
*/
function replace(config = {}) {
const {
resources,
verbose
} = { ...defaultConfig$1,
...config
};
return {
name: "replace",
resolveId: (id, importer) => {
if (!importer) return;
const {
name: idName
} = parse(id);
for (const [from, to] of resources) {
if (from == null || to == null) return;
const {
name: fromName
} = parse(from); // If the id and the from name are the same, we simply need to resolve
// it to the "to name" instead and we have replaced the file.
if (idName === fromName) {
if (verbose) {
console.log(colors.green(`[replace] - Replaced "${id}" with "${to}"\n`));
}
return resolve(to);
}
}
}
};
}
const isProd = process.env.NODE_ENV === "prod" || process.env.NODE_ENV === "production";
const isDev = process.env.NODE_ENV === "dev" || process.env.NODE_ENV === "development";
const isLibrary = process.env.NODE_ENV === "lib" || process.env.NODE_ENV === "library";
const isServe = process.env.ROLLUP_WATCH || false;
/**
* Returns the config or an empty default.
* @param config
*/
const configOrDefault = config => {
return config || {};
};
/**
* The default scss plugins.
*/
const postcssPlugins = [precss(), autoprefixer(), ...(isProd ? [// Currently there's an issue with nested calcs and custom variables.
// It can be reproduced by entering the following in https://cssnano.co/playground/: font-size: calc(var(--padding, calc(24 * var(--base-size, 1px))) + 1);
// cssnano uses the following postcss plugins: https://cssnano.co/guides/optimisations.
// https://cssnano.co/optimisations/calc is therefore probably the cause for this issue.
// Read here for configuration: https://cssnano.co/guides/presets
cssnano({
preset: ["default", {
calc: false
}]
})] : [])];
/**
* Default configuration for the output.
* @param config
*/
const defaultOutputConfig = config => {
// format: (system, amd, cjs, esm, iife, umd)
return {
entryFileNames: "[name]-[hash].js",
chunkFileNames: "[name]-[hash].js",
sourcemap: true,
format: "esm",
...configOrDefault(config)
};
};
/**
* Default plugins for resolve.
* @param importStylesConfig
* @param replaceConfig
* @param tsConfig
* @param commonjsConfig
* @param jsonConfig
* @param resolveConfig
*/
const defaultResolvePlugins = ({
importStylesConfig,
jsonConfig,
resolveConfig,
tsConfig,
commonjsConfig,
replaceConfig
} = {}) => [// Teaches Rollup what files should be replaced
replace({ ...configOrDefault(replaceConfig)
}), // Teaches Rollup how to find external modules
// https://github.com/rollup/rollup-plugin-node-resolve
resolve$1({
modulesOnly: false,
mainFields: ["module", "browser", "jsnext:main"],
...configOrDefault(resolveConfig)
}), // Teaches Rollup how to import styles when using the "import css from "./styles.scss" syntax.
importStyles({
plugins: postcssPlugins,
...configOrDefault(importStylesConfig)
}), // Teaches Rollup how to import json files
json({
preferConst: true,
compact: true,
...configOrDefault(jsonConfig)
}), // Teaches Rollup how to transpile Typescript
// https://github.com/wessberg/rollup-plugin-ts
ts({
/* @ts-ignore */
transpiler: "babel",
...configOrDefault(tsConfig)
}), // At the moment, the majority of packages on NPM are exposed as CommonJS modules
commonjs({
include: "**/node_modules/**",
...configOrDefault(commonjsConfig)
})];
/**
* Default configuration for the plugins that runs every time the bundle is created.
* @param cleanConfig
* @param copyConfig
* @param importStylesConfig
* @param jsonConfig
* @param htmlTemplateConfig
* @param resolveConfig
* @param progressConfig
* @param tsConfig
* @param commonjsConfig
* @param replaceConfig
*/
const defaultPlugins = ({
cleanConfig,
copyConfig,
importStylesConfig,
jsonConfig,
htmlTemplateConfig,
resolveConfig,
progressConfig,
tsConfig,
commonjsConfig,
replaceConfig
} = {}) => [// Shows a progress indicator while building
progress({ ...configOrDefault(progressConfig)
}), // Cleans the dist folder to get rid of files from the previous build
clean({ ...configOrDefault(cleanConfig)
}), // Teach rollup how to resolve imports
...defaultResolvePlugins({
importStylesConfig,
jsonConfig,
resolveConfig,
tsConfig,
commonjsConfig,
replaceConfig
}), // Copies resources
copy({ ...configOrDefault(copyConfig)
}), // Creates a HTML template with the injected scripts from the entry points
htmlTemplate({ ...configOrDefault(htmlTemplateConfig)
})];
/**
* Default plugins that only run when the bundle is being served.
* @param dist
* @param serveConfig
* @param livereloadConfig
*/
const defaultServePlugins = ({
dist,
serveConfig,
livereloadConfig
} = {}) => [// Serves the application files
serve({
open: true,
port: 1337,
historyApiFallback: true,
host: "localhost",
headers: {
"Access-Control-Allow-Origin": "*"
},
...(dist != null ? {
contentBase: dist
} : {}),
...configOrDefault(serveConfig)
}), // Reloads the page when run in watch mode
livereload({ ...(dist != null ? {
watch: dist
} : {}),
...configOrDefault(livereloadConfig)
})];
/**
* Default plugins that only run when the bundle is being created in prod mode.
* @param dist
* @param minifyLitHtmlConfig
* @param licenseConfig
* @param terserConfig
* @param budgetConfig
* @param visualizerConfig
* @param compressConfig
*/
const defaultProdPlugins = ({
dist,
minifyLitHtmlConfig,
licenseConfig,
terserConfig,
budgetConfig,
visualizerConfig,
compressConfig
} = {}) => [// Minifies the lit-html files
minifyLitHTML({ ...configOrDefault(minifyLitHtmlConfig)
}), // Collects all the license files
license({
sourcemap: true,
...(dist != null ? {
thirdParty: {
output: join(dist, "licenses.txt")
}
} : {}),
...configOrDefault(licenseConfig)
}), // Minifies the code
terser({
output: {
// Don't preserve any comments, we create a license.txt instead.
comments: () => false
},
...configOrDefault(terserConfig)
}), // Prints the budget and sizes of the files to the console
budget({ ...configOrDefault(budgetConfig)
}), // Create a HTML file visualizing the size of each module
visualizer({
sourcemap: true,
...(dist != null ? {
filename: join(dist, "stats.html")
} : {}),
...configOrDefault(visualizerConfig)
}), // Compresses all of the files
compress({ ...configOrDefault(compressConfig)
})];
/**
* Default external dependencies.
* @param dependencies
* @param devDependencies
* @param peerDependencies
*/
const defaultExternals = ({
dependencies,
devDependencies,
peerDependencies
}) => [...Object.keys(configOrDefault(dependencies)), ...Object.keys(configOrDefault(devDependencies)), ...Object.keys(configOrDefault(peerDependencies))];
/**
* Creates a default karma configuration.
* @param files
* @param mime
* @param preprocessors
* @param karmaPlugins
* @param rollupPlugins
*/
const defaultKarmaConfig = ({
files,
mime,
preprocessors,
karmaPlugins,
rollupPlugins
} = {}) => {
return {
concurrency: Infinity,
colors: true,
autoWatch: true,
singleRun: true,
captureTimeout: 60000,
browsers: ["ChromeHeadless"],
frameworks: ["mocha", "chai", "iframes"],
reporters: ["progress"],
plugins: ["karma-mocha", "karma-chai", "karma-chrome-launcher", "karma-rollup-preprocessor", "karma-iframes", ...(karmaPlugins || [])],
files: [
/**
* Make sure to disable Karma’s file watcher
* because the preprocessor will use its own.
*/
{
pattern: "**/*.test.+(ts|js)",
watched: false
}, ...(files || [])],
preprocessors: {
"**/*.test.+(ts|js)": ["rollup", "iframes"],
...(preprocessors || {})
},
rollupPreprocessor: {
/**
* This is just a normal Rollup config object,
* except that `input` is handled for you.
*/
plugins: rollupPlugins,
output: {
format: "iife",
name: "wutwut",
sourcemap: "inline"
}
},
// The below line tricks karma into thinking .ts files are cool
// See https://github.com/webpack-contrib/karma-webpack/issues/298#issuecomment-367081075 for more info
mime: {
"text/x-typescript": ["ts"],
...(mime || {})
}
};
};
var GenerateServiceWorkerKind;
(function (GenerateServiceWorkerKind) {
GenerateServiceWorkerKind["generateSw"] = "generateSW";
GenerateServiceWorkerKind["injectManifest"] = "injectManifest";
})(GenerateServiceWorkerKind || (GenerateServiceWorkerKind = {}));
/**
* Default configuration for the workbox rollup plugin.
*/
const defaultConfig = {
mode: GenerateServiceWorkerKind.generateSw,
verbose: true,
timeout: 2000,
workboxConfig: {}
};
/**
* Returns the correct method to for generating the Service Worker.
* @param mode
*/
function workboxFactory(mode) {
switch (mode) {
case GenerateServiceWorkerKind.generateSw:
return generateSW;
case GenerateServiceWorkerKind.injectManifest:
return injectManifest;
}
throw new Error(`[workbox] - The mode "${mode} is not valid"`);
}
/**
* A Rollup plugin that uses workbox to generate a service worker.
* @param config
*/
function workbox(config = {}) {
const {
workboxConfig,
mode,
verbose,
timeout
} = { ...defaultConfig,
...config
}; // Ensure a workbox config exists
if (workboxConfig == null) {
throw new Error(`[workbox] - The workboxConfig needs to be defined`);
}
return {
name: "workbox",
generateBundle: async (outputOptions, bundle, isWrite) => {
if (!isWrite) return;
try {
setTimeout(async () => {
await workboxFactory(mode)(workboxConfig);
}, timeout);
await workboxFactory(mode)(workboxConfig);
} catch (ex) {
if (verbose) {
console.log(colors.red(`[workbox] - The Service Worker could not be generated: "${ex.message}"`));
}
}
}
};
}
export { GenerateServiceWorkerKind, budget, clean, compress, compressBrotli, compressGzip, copy, defaultExternals, defaultKarmaConfig, defaultOutputConfig, defaultPlugins, defaultProdPlugins, defaultResolvePlugins, defaultServePlugins, getPolyfillScript, htmlTemplate, importStyles, isDev, isLibrary, isProd, isServe, livereload, minifyLitHTML, postcssPlugins, replace, transformScript, transformTemplate, workbox };