Skip to content

Commit

Permalink
fix: handle widgets from node_modules
Browse files Browse the repository at this point in the history
  • Loading branch information
janvennemann committed Feb 4, 2020
1 parent 4ed9467 commit c4b7e39
Show file tree
Hide file tree
Showing 4 changed files with 3,772 additions and 113 deletions.
169 changes: 141 additions & 28 deletions lib/plugin.js
@@ -1,12 +1,15 @@
const asyncLib = require('neo-async');
const path = require('path');
const ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency');
const { mergeI18n } = require('./utils');

/**
* Alloy loader plugin
*
* This plugin will do the following:
*
* - Add exclude/include patterns for `alloy/widgets` context require
* - Override the `resolveDependencies` function of `/alloy/widgets/` context
* require to scan all possible widgets paths.
* - Process all available `i18n` directories and add merged .xml files
* as additional assets.
*/
Expand All @@ -17,15 +20,6 @@ class AlloyLoaderPlugin {
}

apply(compiler) {
compiler.hooks.contextModuleFactory.tap('AlloyLoaderPlugin', cmf => {
cmf.hooks.afterResolve.tap('AlloyLoaderPlugin', result => {
if (/alloy[/\\]widgets/.test(result.request)) {
result.exclude = this.generateWidgetsExclude();
result.include = [ /controllers|styles/ ];
}
});
});

compiler.hooks.compilation.tap(
'AlloyLoaderPlugin',
(compilation, { normalModuleFactory }) => {
Expand All @@ -41,9 +35,146 @@ class AlloyLoaderPlugin {
}
);

this.applyWidgetsHandling(compiler);
this.applyI18nHandling(compiler);
}

applyWidgetsHandling(compiler) {
const widgets = this.alloyCompiler.compilationMeta.widgets;
if (widgets.size === 0) {
return;
}

compiler.hooks.contextModuleFactory.tap('AlloyLoaderPlugin', cmf => {
cmf.hooks.afterResolve.tap('AlloyLoaderPlugin', result => {
if (!/alloy[/\\]widgets/.test(result.request)) {
return;
}
result.resolveDependencies = (fs, options, callback) => {
let resource = options.resource;
let resourceQuery = options.resourceQuery;
let regExp = options.regExp;
let include = /(controllers|styles)[/\\]/;
const addDirectory = (directory, callback) => {
fs.readdir(directory, (err, files) => {
if (err) {
return callback(err);
}

files = cmf.hooks.contextModuleFiles.call(files);
if (!files || files.length === 0) {
return callback(null, []);
}

asyncLib.map(
files.filter(p => p.indexOf('.') !== 0),
(entry, callback) => {
const subResource = path.join(directory, entry);
if (directory.endsWith('widgets') && !widgets.has(subResource)) {
return callback();
}

fs.stat(subResource, (err, stat) => {
if (err) {
if (err.code === 'ENOENT') {
// ENOENT is ok here because the file may have been deleted between
// the readdir and stat calls.
return callback();
} else {
return callback(err);
}
}

if (stat.isDirectory()) {
addDirectory.call(null, subResource, callback);
} else if (stat.isFile() && subResource.match(include)) {
let request;
if (/node_modules/.test(subResource)) {
request = subResource.substr(subResource.lastIndexOf('node_modules') + 13);
} else {
request = `.${subResource.substr(resource.length).replace(/\\/g, '/')}`;
}
const obj = {
context: resource,
request
};
if (/node_modules/.test(subResource)) {
obj.request = subResource.substr(subResource.lastIndexOf('node_modules') + 13);
obj.isNpmWidget = true;
}
cmf.hooks.alternatives.callAsync(
[ obj ],
(err, alternatives) => {
if (err) {
return callback(err);
}
alternatives = alternatives
.filter(obj => (obj.isNpmWidget ? true : regExp.test(obj.request)))
.map(obj => {
const userRequest = obj.isNpmWidget
? obj.request.replace(/^alloy-widget-/, './')
: obj.request;
const dep = new ContextElementDependency(
obj.request + resourceQuery,
userRequest
);
dep.optional = true;
return dep;
});
callback(null, alternatives);
}
);
} else {
callback();
}
});
},
(err, result) => {
if (err) {
return callback(err);
}

if (!result) {
return callback(null, []);
}

const dependencies = [];
for (const item of result) {
if (item) {
dependencies.push(...item);
}
}
callback(null, dependencies);
}
);
});
};

const tasks = [];
tasks.push(done => addDirectory(resource, done));
widgets.forEach((widget, widgetPath) => {
if (/node_modules/.test(widgetPath)) {
tasks.push(done => addDirectory(widgetPath, done));
}
});
asyncLib.series(tasks, (err, result) => {
if (err) {
return callback(err);
}

const dependencies = [];
for (const item of result) {
if (item) {
dependencies.push(...item);
}
}
callback(null, dependencies);
});
};
});
});
}

/**
* Initializes the processing of i18n files.
*
Expand Down Expand Up @@ -108,24 +239,6 @@ class AlloyLoaderPlugin {
addDependencies(compilation.fileDependencies, fileDependencies);
});
}

generateWidgetsExclude() {
const widgets = this.alloyCompiler.compilationMeta.widgets;
const exclude = [];
widgets.forEach(widget => {
const validPlatforms = widget.manifest.platforms.split(',');
if (!validPlatforms.includes(this.options.platform)) {
// eslint-disable-next-line security/detect-non-literal-regexp
exclude.push(new RegExp(path.basename(widget.dir).replace(/\./, '\\.')));
}
});
if (exclude.length === 0) {
return null;
}

// eslint-disable-next-line security/detect-non-literal-regexp
return new RegExp(exclude.join('|'));
}
}

module.exports = AlloyLoaderPlugin;
4 changes: 2 additions & 2 deletions lib/utils.js
Expand Up @@ -3,8 +3,8 @@ const globby = require('globby');
const path = require('path');
const { DOMParser, XMLSerializer } = require('xmldom');

const entryRegex = /app[/\\]alloy.js/;
const internalsRegex = /node_modules[/\\]alloy/;
const entryRegex = /app[/\\]alloy.js$/;
const internalsRegex = /node_modules[/\\]alloy[/\\]/;
const componentRegex = /(?:[/\\]widgets[/\\][^/\\]+)?[/\\](?:controllers|views)[/\\](.*)/;
const modelRegex = /(?:[/\\]widgets[/\\][^/\\]+)?[/\\]models[/\\](.*)/;

Expand Down

0 comments on commit c4b7e39

Please sign in to comment.