aurelia-webpack-plugin
Version:
A plugin for webpack that enables bundling Aurelia applications.
284 lines (283 loc) • 14.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.AureliaPlugin = void 0;
const webpack_1 = require("webpack");
const AureliaDependenciesPlugin_1 = require("./AureliaDependenciesPlugin");
const ConventionDependenciesPlugin_1 = require("./ConventionDependenciesPlugin");
const DistPlugin_1 = require("./DistPlugin");
const GlobDependenciesPlugin_1 = require("./GlobDependenciesPlugin");
const HtmlDependenciesPlugin_1 = require("./HtmlDependenciesPlugin");
const InlineViewDependenciesPlugin_1 = require("./InlineViewDependenciesPlugin");
const ModuleDependenciesPlugin_1 = require("./ModuleDependenciesPlugin");
const PreserveExportsPlugin_1 = require("./PreserveExportsPlugin");
const PreserveModuleNamePlugin_1 = require("./PreserveModuleNamePlugin");
const SubFolderPlugin_1 = require("./SubFolderPlugin");
const Webpack = require("webpack");
// See comments inside the module to understand why this is used
const emptyEntryModule = "aurelia-webpack-plugin/runtime/empty-entry";
class AureliaPlugin {
constructor(options = {}) {
const opts = this.options = Object.assign({
includeAll: false,
aureliaConfig: ["standard", "developmentLogging"],
dist: "native-modules",
features: {},
moduleMethods: [],
noHtmlLoader: false,
// Undocumented safety switch
noInlineView: false,
noModulePathResolve: false,
noWebpackLoader: false,
// Ideally we would like _not_ to process conventions in node_modules,
// because they should be using @useView and not rely in convention in
// the first place. Unfortunately at this point many libs do use conventions
// so it's just more helpful for users to process them.
// As unlikely as it may seem, a common offender here is tslib, which has
// matching (yet unrelated) html files in its distribution. So I am making
// a quick exception for that.
viewsFor: "**/!(tslib)*.{ts,js}",
viewsExtensions: ".html",
}, options);
if (opts.entry) {
opts.entry = Array.isArray(opts.entry) ? opts.entry : [opts.entry];
}
opts.features = Object.assign({
ie: true,
svg: true,
unparser: true,
polyfills: "es2015",
}, options.features);
}
apply(compiler) {
const opts = this.options;
const features = opts.features;
let needsEmptyEntry = false;
let dllPlugin = compiler.options.plugins.some(p => p instanceof webpack_1.DllPlugin);
let dllRefPlugins = compiler.options.plugins.filter(p => p instanceof webpack_1.DllReferencePlugin);
// Make sure the loaders are easy to load at the root like `aurelia-webpack-plugin/html-resource-loader`
let resolveLoader = compiler.options.resolveLoader;
let alias = resolveLoader.alias || (resolveLoader.alias = {});
alias["aurelia-webpack-plugin"] = "aurelia-webpack-plugin/dist";
// Our async! loader is in fact just bundle-loader!.
alias["async"] = "aurelia-webpack-plugin/dist/bundle-loader";
// For my own sanity when working on this plugin with `yarn link`,
// make sure neither webpack nor Node resolve symlinks.
if ("NODE_PRESERVE_SYMLINKS" in process.env) {
resolveLoader.symlinks = false;
compiler.options.resolve.symlinks = false;
}
// If we aren't building a DLL, "main" is the default entry point
// Note that the 'in' check is because someone may explicitly set aureliaApp to undefined
if (!dllPlugin && !("aureliaApp" in opts)) {
opts.aureliaApp = "main";
}
// Uses DefinePlugin to cut out optional features
const defines = {
AURELIA_WEBPACK_2_0: "true"
};
if (!features.ie)
defines.FEATURE_NO_IE = "true";
if (!features.svg)
defines.FEATURE_NO_SVG = "true";
if (!features.unparser)
defines.FEATURE_NO_UNPARSER = "true";
definePolyfills(defines, features.polyfills);
// Add some dependencies that are not documented with PLATFORM.moduleName
// because they are determined at build-time.
const dependencies = {
// PAL for target
"aurelia-bootstrapper": "pal" in opts ? opts.pal : { name: getPAL(compiler.options.target), exports: ['initialize'] },
// `aurelia-framework` exposes configuration helpers like `.standardConfiguration()`,
// that load plugins, but we can't know if they are actually used or not.
// User indicates what he uses at build time in `aureliaConfig` option.
// Custom config is performed in use code and can use `.moduleName()` like normal.
"aurelia-framework": getConfigModules(opts.aureliaConfig),
};
let globalDependencies = [];
if (opts.dist) {
// This plugin enables easy switching to a different module distribution (default for Aurelia is dist/commonjs).
let resolve = compiler.options.resolve;
let plugins = resolve.plugins || (resolve.plugins = []);
plugins.push(new DistPlugin_1.DistPlugin(opts.dist));
}
if (!opts.noModulePathResolve) {
// This plugin enables sub-path in modules that are not at the root (e.g. in a /dist folder),
// for example aurelia-chart/pie might resolve to aurelia-chart/dist/commonjs/pie
let resolve = compiler.options.resolve;
let plugins = resolve.plugins || (resolve.plugins = []);
plugins.push(new SubFolderPlugin_1.SubFolderPlugin());
}
if (opts.includeAll) {
// Grab everything approach
// This plugin ensures that everything in /src is included in the bundle.
// This prevents splitting in several chunks but is super easy to use and setup,
// no change in existing code or PLATFORM.nameModule() calls are required.
new GlobDependenciesPlugin_1.GlobDependenciesPlugin({ [emptyEntryModule]: opts.includeAll + "/**" }).apply(compiler);
needsEmptyEntry = true;
}
else if (opts.aureliaApp) {
// Add aurelia-app entry point.
// When using includeAll, we assume it's already included
globalDependencies.push({ name: opts.aureliaApp, exports: ["configure"] });
}
if (!dllPlugin && dllRefPlugins.length > 0) {
// Creates delegated entries for all Aurelia modules in DLLs.
// This is required for aurelia-loader-webpack to find them.
let aureliaModules = dllRefPlugins.map(plugin => {
let content = plugin["options"].manifest.content;
return Object.keys(content)
.map(k => content[k].buildMeta["aurelia-id"])
.filter(id => !!id);
});
globalDependencies = globalDependencies.concat(...aureliaModules);
}
if (!dllPlugin && !opts.noWebpackLoader) {
// Setup aurelia-loader-webpack as the module loader
this.addEntry(compiler.options, ["aurelia-webpack-plugin/runtime/pal-loader-entry"]);
}
if (!opts.noHtmlLoader) {
// Ensure that we trace HTML dependencies (always required because of 3rd party libs)
// Note that this loader will be in last place, which is important
// because it will process the file first, before any other loader.
compiler.options.module.rules.push({ test: /\.html?$/i, use: "aurelia-webpack-plugin/html-requires-loader" });
}
if (!opts.noInlineView) {
new InlineViewDependenciesPlugin_1.InlineViewDependenciesPlugin().apply(compiler);
}
if (globalDependencies.length > 0) {
dependencies[emptyEntryModule] = globalDependencies;
needsEmptyEntry = true;
}
if (needsEmptyEntry) {
this.addEntry(compiler.options, emptyEntryModule);
}
compiler.hooks.compilation.tap('AureliaPlugin', (compilation, params) => {
compilation.hooks.runtimeRequirementInTree
.for(Webpack.RuntimeGlobals.require)
.tap('AureliaPlugin', (chunk) => {
compilation.addRuntimeModule(chunk, new AureliaExposeWebpackInternal());
});
});
// Aurelia libs contain a few global defines to cut out unused features
new webpack_1.DefinePlugin(defines).apply(compiler);
// Adds some dependencies that are not documented by `PLATFORM.moduleName`
new ModuleDependenciesPlugin_1.ModuleDependenciesPlugin(dependencies).apply(compiler);
// This plugin traces dependencies in code that are wrapped in PLATFORM.moduleName() calls
new AureliaDependenciesPlugin_1.AureliaDependenciesPlugin(...opts.moduleMethods).apply(compiler);
// This plugin adds dependencies traced by html-requires-loader
// Note: the config extension point for this one is html-requires-loader.attributes.
new HtmlDependenciesPlugin_1.HtmlDependenciesPlugin().apply(compiler);
// This plugin looks for companion files by swapping extensions,
// e.g. the view of a ViewModel. @useView and co. should use PLATFORM.moduleName().
// We use it always even with `includeAll` because libs often don't `@useView` (they should).
new ConventionDependenciesPlugin_1.ConventionDependenciesPlugin(opts.viewsFor, opts.viewsExtensions).apply(compiler);
// This plugin preserves module names for dynamic loading by aurelia-loader
new PreserveModuleNamePlugin_1.PreserveModuleNamePlugin(dllPlugin).apply(compiler);
// This plugin supports preserving specific exports names when dynamically loading modules
// with aurelia-loader, while still enabling tree shaking all other exports.
new PreserveExportsPlugin_1.PreserveExportsPlugin().apply(compiler);
}
addEntry(options, modules) {
var _a, _b;
let webpackEntry = options.entry;
let entries = Array.isArray(modules) ? modules : [modules];
// todo:
// probably cant do much here?
if (typeof webpackEntry === 'function') {
return;
}
// If entry point unspecified in plugin options; use the first webpack entry point
const pluginEntry = (_a = this.options.entry) !== null && _a !== void 0 ? _a : [Object.keys(webpackEntry)[0]];
// Ensure array
const pluginEntries = Array.isArray(pluginEntry) ? pluginEntry : [pluginEntry];
// Add runtime to each entry
for (const k of pluginEntries) {
// Verify that plugin options entry points are defined in webpack entry points
if (!(k in webpackEntry)) {
throw new Error('entry key "' + k + '" is not defined in Webpack build, cannot apply runtime.');
}
let entry = webpackEntry[k];
(_b = entry.import) === null || _b === void 0 ? void 0 : _b.unshift(...entries);
}
}
}
exports.AureliaPlugin = AureliaPlugin;
;
function getPAL(target) {
if (target instanceof Array) {
if (target.includes('web') || target.includes('es5')) {
return 'aurelia-pal-browser';
}
// not really sure what to pick from an array
target = target[0];
}
if (target === false) {
throw new Error('Invalid target to build for AureliaPlugin.');
}
switch (target) {
case undefined:
case "es5":
case "web":
return "aurelia-pal-browser";
case "webworker": return "aurelia-pal-worker";
case "electron-renderer": return "aurelia-pal-browser";
default: return "aurelia-pal-nodejs";
}
}
const configModules = {};
let configModuleNames = {
"defaultBindingLanguage": "aurelia-templating-binding",
"router": "aurelia-templating-router",
"history": "aurelia-history-browser",
"defaultResources": "aurelia-templating-resources",
"eventAggregator": "aurelia-event-aggregator",
"developmentLogging": "aurelia-logging-console",
};
// "configure" is the only method used by .plugin()
for (let c in configModuleNames)
configModules[c] = { name: configModuleNames[c], exports: ["configure"] };
// developmentLogging has a pre-task that uses ConsoleAppender
configModules["developmentLogging"].exports.push("ConsoleAppender");
function getConfigModules(config) {
if (!config)
return undefined;
if (!Array.isArray(config))
config = [config];
// Expand "standard"
let i = config.indexOf("standard");
if (i >= 0)
config.splice(i, 1, "basic", "history", "router");
// Expand "basic"
i = config.indexOf("basic");
if (i >= 0)
config.splice(i, 1, "defaultBindingLanguage", "defaultResources", "eventAggregator");
return config.map(c => configModules[c]);
}
function definePolyfills(defines, polyfills) {
if (polyfills === "es2015")
return;
defines.FEATURE_NO_ES2015 = "true";
if (polyfills === "es2016")
return;
defines.FEATURE_NO_ES2016 = "true";
if (polyfills === "esnext")
return;
defines.FEATURE_NO_ESNEXT = "true";
// "none" or invalid option.
}
class AureliaExposeWebpackInternal extends Webpack.RuntimeModule {
constructor() {
super("Aurelia expose webpack internal");
}
/**
* @returns {string} runtime code
*/
generate() {
return Webpack.Template.asString([
"if (typeof __webpack_modules__ !== 'undefined') {",
"__webpack_require__.m = __webpack_require__.m || __webpack_modules__;",
"__webpack_require__.c = __webpack_require__.c || __webpack_module_cache__;",
"}",
]);
}
}
;