@enact/dev-utils
Version:
A collection of development utilities for Enact apps.
173 lines (154 loc) • 6.61 kB
JavaScript
const fs = require('fs');
const path = require('path');
const gracefulFs = require('graceful-fs');
const DelegatedSourceDependency = require('webpack/lib/dependencies/DelegatedSourceDependency');
const DelegatedModule = require('webpack/lib/DelegatedModule');
const ExternalsPlugin = require('webpack/lib/ExternalsPlugin');
const app = require('../../option-parser');
const findParentMain = function (dir) {
const currPkg = path.join(dir, 'package.json');
if (fs.existsSync(currPkg)) {
const meta = JSON.parse(fs.readFileSync(currPkg, {encoding: 'utf8'}));
if (meta.main) return {path: dir, pointsTo: path.join(dir, meta.main).replace(/\.js/, '')};
}
if (dir === '/' || dir === '' || dir === '.') return null;
return findParentMain(path.dirname(dir));
};
// Custom DelegateFactoryPlugin designed to redirect Enact framework require() calls
// to the external framework
class DelegatedEnactFactoryPlugin {
constructor(options = {}) {
this.options = options;
this.options.local = this.options.libraries.includes('.');
if (this.options.local) this.options.libraries.splice(this.options.libraries.indexOf('.'), 1);
}
apply(normalModuleFactory) {
const {name, libraries, ignore, local, polyfill} = this.options;
const libReg = new RegExp('^(' + libraries.join('|') + ')(?=[\\\\\\/]|$)');
const ignReg =
ignore && new RegExp('^(' + ignore.map(p => p.replace('/', '[\\\\\\/]')).join('|') + ')(?=[\\\\\\/]|$)');
normalModuleFactory.hooks.factorize.tapAsync('DelegatedEnactFactoryPlugin', (data, callback) => {
const dependency = data.dependencies[0];
const {context} = data;
const {request} = dependency;
if (request === polyfill) {
const polyID = '@enact/polyfills';
return callback(null, new DelegatedModule(name, {id: polyID}, 'require', polyID, polyID));
} else if (local && request && context && request.startsWith('.')) {
let resource = path.join(context, request);
if (
resource.startsWith(app.context) &&
!/[\\/]tests[\\/]/.test('./' + path.relative(app.context, resource)) &&
(!ignReg || !ignReg.test(resource.replace(/^(.*[\\/]node_modules[\\/])+/, '')))
) {
const parent = findParentMain(path.dirname(resource));
if (parent.pointsTo === resource) resource = parent.path;
let localID = resource
.replace(app.context, app.name)
.replace(/\.js$/, '')
.replace(/\\/g, '/')
.replace(app.name + '/node_modules/', '')
.replace(/[\\/]$/, '');
return callback(null, new DelegatedModule(name, {id: localID}, 'require', localID, localID));
}
}
if (request && libReg.test(request) && (!ignReg || !ignReg.test(request))) {
return callback(null, new DelegatedModule(name, {id: request}, 'require', request, request));
}
return callback();
});
}
}
// Form a correct filepath that can be used within the build's output directory
function normalizePath(dir, file, compiler) {
if (path.isAbsolute(dir)) {
return path.join(dir, file);
} else {
return path.relative(path.resolve(compiler.outputPath), path.join(process.cwd(), dir, file));
}
}
// Determine if it's a NodeJS output filesystem or if it's a foreign/virtual one.
// The internal webpack5 implementation of outputFileSystem is graceful-fs.
function isNodeOutputFS(compiler) {
return compiler.outputFileSystem && JSON.stringify(compiler.outputFileSystem) === JSON.stringify(gracefulFs);
}
// Reference plugin to handle rewiring the external Enact framework requests
class EnactFrameworkRefPlugin {
constructor(options = {}) {
this.options = options;
this.options.name = this.options.name || 'enact_framework';
this.options.libraries = this.options.libraries || [
'@enact',
'react',
'react-dom',
'react-dom/client',
'react-dom/server',
'ilib'
];
this.options.ignore = this.options.ignore || [
'@enact/dev-utils',
'@enact/storybook-utils',
'@enact/ui-test-utils',
'@enact/screenshot-test-utils',
'readable-stream', // ignore for screenshot test build
'react-is' // ignore for ui test build
];
this.options.external = this.options.external || {};
this.options.external.publicPath =
this.options.publicPath || this.options.external.publicPath || this.options.external.path;
if (!this.options.htmlPlugin) this.options.htmlPlugin = require('html-webpack-plugin');
if (!process.env.ILIB_BASE_PATH) {
// Backwards support for Enact <3
const context = options.context || process.cwd();
if (fs.existsSync(path.join(context, 'node_modules', '@enact', 'i18n', 'ilib'))) {
process.env.ILIB_BASE_PATH = path.join(
this.options.external.publicPath,
'node_modules',
'@enact',
'i18n',
'ilib'
);
} else {
process.env.ILIB_BASE_PATH = path.join(this.options.external.publicPath, 'node_modules', 'ilib');
}
}
}
apply(compiler) {
const {name, libraries, ignore, external, polyfill, htmlPlugin, webOSMetaPlugin} = this.options;
// Declare enact_framework as an external dependency
const externals = {};
externals[this.options.name] = this.options.name;
new ExternalsPlugin(this.options.libraryTarget || 'var', externals).apply(compiler);
compiler.hooks.compilation.tap('EnactFrameworkRefPlugin', (compilation, {normalModuleFactory}) => {
const htmlPluginHooks = htmlPlugin.getHooks(compilation);
const webOSMetaPluginHooks = webOSMetaPlugin.getHooks(compilation);
compilation.dependencyFactories.set(DelegatedSourceDependency, normalModuleFactory);
htmlPluginHooks.beforeAssetTagGeneration.tapAsync('EnactFrameworkRefPlugin', (htmlPluginData, callback) => {
htmlPluginData.assets.js.unshift(
normalizePath(external.publicPath, 'enact.js', compiler).replace(/\\+/g, '/')
);
htmlPluginData.assets.css.unshift(
normalizePath(external.publicPath, 'enact.css', compiler).replace(/\\+/g, '/')
);
callback(null, htmlPluginData);
});
if (external.snapshot && isNodeOutputFS(compiler) && webOSMetaPluginHooks.webosMetaRootAppinfo) {
webOSMetaPluginHooks.webosMetaRootAppinfo.tap('EnactFrameworkRefPlugin', meta => {
const relSnap = normalizePath(external.publicPath, 'snapshot_blob.bin', compiler);
meta.v8SnapshotFile = relSnap.replace(/\\+/g, '/');
return meta;
});
}
});
// Apply the Enact factory plugin to handle the require() delagation/rerouting
compiler.hooks.compile.tap('EnactFrameworkRefPlugin', ({normalModuleFactory}) => {
new DelegatedEnactFactoryPlugin({
name,
libraries,
ignore,
polyfill
}).apply(normalModuleFactory);
});
}
}
module.exports = EnactFrameworkRefPlugin;