Skip to content

Commit

Permalink
feat(webpack): support Webpack 4
Browse files Browse the repository at this point in the history
This is not compatible with Webpack 3 or 2 anymore.
Fixed crashes caused by API changes.
Updated typings.
Stopped using deprecated APIs.

Closes #138.
  • Loading branch information
jods4 committed Feb 26, 2018
1 parent 5177bb3 commit e49fb8d
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 237 deletions.
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aurelia-webpack-plugin",
"version": "2.0.0-rc.5",
"version": "2.0.0-rc.6",
"description": "A plugin for webpack that enables bundling Aurelia applications.",
"keywords": [
"aurelia",
Expand Down Expand Up @@ -35,17 +35,17 @@
},
"dependencies": {
"aurelia-loader-webpack": "^2.1.0",
"bundle-loader": "^0.5.5",
"html-loader": "^0.4.5",
"bundle-loader": "^0.5.6",
"html-loader": "^0.5.5",
"minimatch": "^3.0.4"
},
"peerDependencies": {
"webpack": ">= 2.2.0"
"webpack": ">= 4.0.0"
},
"devDependencies": {
"@types/minimatch": "^2.0.29",
"@types/node": "^7.0.28",
"typescript": "^2.3.4"
"@types/minimatch": "^3.0.3",
"@types/node": "^9.4.6",
"typescript": "^2.7.2"
},
"aurelia": {
"documentation": {
Expand Down
18 changes: 11 additions & 7 deletions src/AureliaDependenciesPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IncludeDependency } from "./IncludeDependency";
import BasicEvaluatedExpression = require("webpack/lib/BasicEvaluatedExpression");

const TAP_NAME = "Aurelia:Dependencies";

class AureliaDependency extends IncludeDependency {
constructor(request: string,
public range: [number, number],
Expand All @@ -25,16 +27,18 @@ class ParserPlugin {
let dep = new AureliaDependency(module, range, options);
parser.state.current.addDependency(dep);
return true;
}
}

// The parser will only apply "call PLATFORM.moduleName" on free variables.
// So we must first trick it into thinking PLATFORM.moduleName is an unbound identifier
// in the various situations where it is not.

const hooks = parser.hooks;

// This covers native ES module, for example:
// import { PLATFORM } from "aurelia-pal";
// PLATFORM.moduleName("id");
parser.plugin("evaluate Identifier imported var.moduleName", (expr: Webpack.MemberExpression) => {
hooks.evaluateIdentifier.tap("imported var.moduleName", TAP_NAME, (expr: Webpack.MemberExpression) => {
if (expr.property.name === "moduleName" &&
expr.object.name === "PLATFORM" &&
expr.object.type === "Identifier") {
Expand All @@ -49,7 +53,7 @@ class ParserPlugin {
// Or (note: no renaming supported):
// const PLATFORM = require("aurelia-pal").PLATFORM;
// PLATFORM.moduleName("id");
parser.plugin("evaluate MemberExpression", (expr: Webpack.MemberExpression) => {
hooks.evaluate.tap("MemberExpression", TAP_NAME, expr => {
if (expr.property.name === "moduleName" &&
(expr.object.type === "MemberExpression" && expr.object.property.name === "PLATFORM" ||
expr.object.type === "Identifier" && expr.object.name === "PLATFORM")) {
Expand All @@ -59,7 +63,7 @@ class ParserPlugin {
});

for (let method of this.methods) {
parser.plugin("call " + method, (expr: Webpack.CallExpression) => {
hooks.call.tap(method, TAP_NAME, (expr: Webpack.CallExpression) => {
if (expr.arguments.length === 0 || expr.arguments.length > 2)
return;

Expand Down Expand Up @@ -119,14 +123,14 @@ export class AureliaDependenciesPlugin {
}

apply(compiler: Webpack.Compiler) {
compiler.plugin("compilation", (compilation, params) => {
compiler.hooks.compilation.tap(TAP_NAME, (compilation, params) => {
const normalModuleFactory = params.normalModuleFactory;

compilation.dependencyFactories.set(AureliaDependency, normalModuleFactory);
compilation.dependencyTemplates.set(AureliaDependency, new Template());

normalModuleFactory.plugin("parser", parser => {
parser.apply(this.parserPlugin);
normalModuleFactory.hooks.parser.for("javascript/auto").tap(TAP_NAME, parser => {
this.parserPlugin.apply(parser);
});
});
}
Expand Down
55 changes: 26 additions & 29 deletions src/AureliaPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,11 @@ export class AureliaPlugin {

if (opts.includeAll) {
// Grab everything approach
compiler.apply(
// 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({ [emptyEntryModule]: opts.includeAll + "/**" })
);

// 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({ [emptyEntryModule]: opts.includeAll + "/**" }).apply(compiler);
needsEmptyEntry = true;
}
else if (opts.aureliaApp) {
Expand All @@ -160,7 +159,7 @@ export class AureliaPlugin {
let aureliaModules = dllRefPlugins.map(plugin => {
let content = plugin["options"].manifest.content;
return Object.keys(content)
.map(k => content[k].meta["aurelia-id"])
.map(k => content[k].buildMeta["aurelia-id"])
.filter(id => !!id) as string[];
});
globalDependencies = globalDependencies.concat(...aureliaModules);
Expand All @@ -181,7 +180,7 @@ export class AureliaPlugin {
}

if (!opts.noInlineView) {
compiler.apply(new InlineViewDependenciesPlugin());
new InlineViewDependenciesPlugin().apply(compiler);
}

if (globalDependencies.length > 0) {
Expand All @@ -192,27 +191,25 @@ export class AureliaPlugin {
if (needsEmptyEntry) {
this.addEntry(compiler.options, emptyEntryModule);
}

compiler.apply(
// Aurelia libs contain a few global defines to cut out unused features
new DefinePlugin(defines),
// Adds some dependencies that are not documented by `PLATFORM.moduleName`
new ModuleDependenciesPlugin(dependencies),
// This plugin traces dependencies in code that are wrapped in PLATFORM.moduleName() calls
new AureliaDependenciesPlugin(...opts.moduleMethods),
// This plugin adds dependencies traced by html-requires-loader
// Note: the config extension point for this one is html-requires-loader.attributes.
new HtmlDependenciesPlugin(),
// 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(opts.viewsFor, opts.viewsExtensions),
// This plugin preserves module names for dynamic loading by aurelia-loader
new PreserveModuleNamePlugin(dllPlugin),
// This plugin supports preserving specific exports names when dynamically loading modules
// with aurelia-loader, while still enabling tree shaking all other exports.
new PreserveExportsPlugin(),
);

// Aurelia libs contain a few global defines to cut out unused features
new DefinePlugin(defines).apply(compiler);
// Adds some dependencies that are not documented by `PLATFORM.moduleName`
new ModuleDependenciesPlugin(dependencies).apply(compiler);
// This plugin traces dependencies in code that are wrapped in PLATFORM.moduleName() calls
new 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().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(opts.viewsFor, opts.viewsExtensions).apply(compiler);
// This plugin preserves module names for dynamic loading by aurelia-loader
new 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().apply(compiler);
}

addEntry(options: Webpack.Options, modules: string|string[]) {
Expand Down
6 changes: 4 additions & 2 deletions src/BaseIncludePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { IncludeDependency } from "./IncludeDependency";
import NullDependency = require("webpack/lib/dependencies/NullDependency");

const TAP_NAME = "Aurelia:BaseInclude";

export type AddDependency = (request: string | DependencyOptionsEx) => void;

export class BaseIncludePlugin {
apply(compiler: Webpack.Compiler) {
compiler.plugin("compilation", (compilation, data) => {
compiler.hooks.compilation.tap(TAP_NAME, (compilation, data) => {
const normalModuleFactory = data.normalModuleFactory;
compilation.dependencyFactories.set(IncludeDependency, normalModuleFactory);
compilation.dependencyTemplates.set(IncludeDependency, new NullDependency.Template());

normalModuleFactory.plugin("parser", parser => {
normalModuleFactory.hooks.parser.for("javascript/auto").tap(TAP_NAME, parser => {
function addDependency(request: string | DependencyOptionsEx) {
let options = typeof request === 'object' ? request : undefined;
let name = options ? options.name : (<string>request);
Expand Down
2 changes: 1 addition & 1 deletion src/ConventionDependenciesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class ConventionDependenciesPlugin extends BaseIncludePlugin {
parser(compilation: Webpack.Compilation, parser: Webpack.Parser, addDependency: AddDependency) {
const root = path.resolve();

parser.plugin("program", () => {
parser.hooks.program.tap("Aurelia:ConventionDependencies", () => {
const { resource: file, rawRequest } = parser.state.current;
if (!file) return;
// We don't want to bring in dependencies of the async! loader
Expand Down
5 changes: 3 additions & 2 deletions src/DistPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ export class DistPlugin {

apply(resolver: Webpack.Resolver) {
if (!this.dist) return;
resolver.plugin("before-described-resolve", (request, cb) => {
resolver.getHook("before-described-resolve")
.tapAsync("Aurelia:Dist", (request: Webpack.ResolveRequest, resolveContext: object, cb: (err?: any, result?: any) => void) => {
// If the request contains /dist/xxx/, try /dist/{dist}/ first
let rewritten = request.request.replace(/\/dist\/[^/]+\//i, this.dist);
if (rewritten !== request.request) {
let newRequest = Object.assign({}, request, { request: rewritten });
resolver.doResolve("described-resolve", newRequest, "try alternate " + this.dist, cb);
resolver.doResolve(resolver.getHook("described-resolve"), newRequest, "try alternate " + this.dist, {}, cb);
}
else
cb(); // Path does not contain /dist/xxx/, continue normally
Expand Down
23 changes: 12 additions & 11 deletions src/GlobDependenciesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { BaseIncludePlugin, AddDependency } from "./BaseIncludePlugin";
import { Minimatch } from "minimatch";
import path = require("path");

const TAP_NAME = "Aurelia:GlobDependencies";

declare module "minimatch" {
interface IMinimatch {
match(fname: string, partial: boolean): boolean; // Missing overload in current minimatch tds
Expand Down Expand Up @@ -56,19 +58,18 @@ export class GlobDependenciesPlugin extends BaseIncludePlugin {
const hashKeys = Object.getOwnPropertyNames(this.hash);
if (hashKeys.length === 0) return;

compiler.plugin("before-compile", (params, cb) => {
compiler.hooks.beforeCompile.tapPromise(TAP_NAME, () => {
// Map the modules passed in ctor to actual resources (files) so that we can
// recognize them no matter what the rawRequest was (loaders, relative paths, etc.)
this.modules = { };
const resolve: (request: string, cb: (err: any, resource: string) => void) => void =
compiler.resolvers.normal.resolve.bind(compiler.resolvers.normal, null, this.root);
let countdown = hashKeys.length;
for (let module of hashKeys) {
resolve(module, (err, resource) => {
this.modules[resource] = this.hash[module];
if (--countdown === 0) cb();
});
}
const resolver = compiler.resolverFactory.get("normal", {});
return Promise.all(
hashKeys.map(module => new Promise(resolve => {
resolver.resolve(null, this.root, module, {}, (err, resource) => {
this.modules[resource] = this.hash[module];
resolve();
});
})));
});

super.apply(compiler);
Expand All @@ -82,7 +83,7 @@ export class GlobDependenciesPlugin extends BaseIncludePlugin {
.filter(x => !x.startsWith(".."))
.map(x => new RegExp("^" + x + "/", "ig"));

parser.plugin("program", () => {
parser.hooks.program.tap(TAP_NAME, () => {
const globs = this.modules[parser.state.module.resource];
if (!globs) return;

Expand Down
2 changes: 1 addition & 1 deletion src/HtmlDependenciesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { htmlSymbol } from "./html-requires-loader";

export class HtmlDependenciesPlugin extends BaseIncludePlugin {
parser(compilation: Webpack.Compilation, parser: Webpack.Parser, addDependency: AddDependency) {
parser.plugin("program", () => {
parser.hooks.program.tap("Aurelia:HtmlDependencies", () => {
const deps = parser.state.current[htmlSymbol];
if (!deps) return;
deps.forEach(addDependency);
Expand Down
8 changes: 5 additions & 3 deletions src/InlineViewDependenciesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { BaseIncludePlugin, AddDependency } from "./BaseIncludePlugin";
import BasicEvaluatedExpression = require("webpack/lib/BasicEvaluatedExpression");
import htmlLoader = require("./html-requires-loader");

const TAP_NAME = "Aurelia:InlineViewDependencies";

export class InlineViewDependenciesPlugin extends BaseIncludePlugin {
parser(compilation: Webpack.Compilation, parser: Webpack.Parser, add: AddDependency) {
// The parser will only apply "call inlineView" on free variables.
Expand All @@ -13,7 +15,7 @@ export class InlineViewDependenciesPlugin extends BaseIncludePlugin {
// This covers native ES module, for example:
// import { inlineView } from "aurelia-framework";
// inlineView("<template>");
parser.plugin("evaluate Identifier imported var", (expr: Webpack.IdentifierExpression) => {
parser.hooks.evaluateIdentifier.tap("imported var", TAP_NAME, (expr: Webpack.IdentifierExpression) => {
if (expr.name === "inlineView") {
return new BasicEvaluatedExpression().setIdentifier("inlineView").setRange(expr.range);
}
Expand All @@ -26,14 +28,14 @@ export class InlineViewDependenciesPlugin extends BaseIncludePlugin {
// Or (note: no renaming supported):
// const inlineView = require("aurelia-framework").inlineView;
// inlineView("<template>");
parser.plugin("evaluate MemberExpression", (expr: Webpack.MemberExpression) => {
parser.hooks.evaluate.tap("MemberExpression", TAP_NAME, expr => {
if (expr.property.name === "inlineView") {
return new BasicEvaluatedExpression().setIdentifier("inlineView").setRange(expr.range);
}
return undefined;
});

parser.plugin("call inlineView", (expr: Webpack.CallExpression) => {
parser.hooks.call.tap("inlineView", TAP_NAME, expr => {
if (expr.arguments.length !== 1)
return;

Expand Down
24 changes: 13 additions & 11 deletions src/ModuleDependenciesPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BaseIncludePlugin, AddDependency } from "./BaseIncludePlugin";
import path = require("path");

const TAP_NAME = "Aurelia:ModuleDependencies";

export interface ModuleDependenciesPluginOptions {
[module: string]:
undefined |
Expand Down Expand Up @@ -39,26 +41,26 @@ export class ModuleDependenciesPlugin extends BaseIncludePlugin {
const hashKeys = Object.getOwnPropertyNames(this.hash);
if (hashKeys.length === 0) return;

compiler.plugin("before-compile", (params, cb) => {
compiler.hooks.beforeCompile.tapPromise(TAP_NAME, () => {
// Map the modules passed in ctor to actual resources (files) so that we can
// recognize them no matter what the rawRequest was (loaders, relative paths, etc.)
this.modules = { };
const resolve: (module: string, cb: (err: any, resource: string) => void) => void =
compiler.resolvers.normal.resolve.bind(compiler.resolvers.normal, null, this.root);
let countdown = hashKeys.length;
for (let module of hashKeys) {
resolve(module, (err, resource) => {
this.modules[resource] = this.hash[module];
if (--countdown === 0) cb();
});
}
const resolver = compiler.resolverFactory.get("normal", {});
return Promise.all(
hashKeys.map(module => new Promise(resolve => {
resolver.resolve(null, this.root, module, {}, (err, resource) => {
this.modules[resource] = this.hash[module];
resolve();
});
})
));
});

super.apply(compiler);
}

parser(compilation: Webpack.Compilation, parser: Webpack.Parser, addDependency: AddDependency) {
parser.plugin("program", () => {
parser.hooks.program.tap(TAP_NAME, () => {
// We try to match the resource, or the initial module request.
const deps = this.modules[parser.state.module.resource];
if (deps) deps.forEach(addDependency);
Expand Down
6 changes: 4 additions & 2 deletions src/PreserveExportsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const dependencyImports = Symbol();
const moduleExports = Symbol();
const nativeIsUsed = Symbol();

const TAP_NAME = "Aurelia:PreserveExports";

function getModuleExports(module: Webpack.Module) {
let set = module[moduleExports];
if (!set) {
Expand All @@ -18,8 +20,8 @@ function getModuleExports(module: Webpack.Module) {

export class PreserveExportsPlugin {
apply(compiler: Webpack.Compiler) {
compiler.plugin("compilation", compilation => {
compilation.plugin("finish-modules", modules => {
compiler.hooks.compilation.tap(TAP_NAME, compilation => {
compilation.hooks.finishModules.tap(TAP_NAME, modules => {
for (let module of modules) {
for (let reason of module.reasons) {
let dep = reason.dependency;
Expand Down
Loading

0 comments on commit e49fb8d

Please sign in to comment.