Skip to content

Instantly share code, notes, and snippets.

@MrBr
Created October 7, 2019 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MrBr/e641eb3cf882a150ce10eb69aef83f0d to your computer and use it in GitHub Desktop.
Save MrBr/e641eb3cf882a150ce10eb69aef83f0d to your computer and use it in GitHub Desktop.
Webpack plugin for importing split library bundles within CRA environment.
const fs = require('fs');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// Used to replace vendor imports with empty string
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
function hashString(str) {
var hash = 0,
i,
chr;
if (str.length === 0) return hash;
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
const VENDORS_INJECTION_PLUGIN = 'VENDORS_INJECTION_PLUGIN';
/**
* Intercept vendor imports and inject them to the head tag.
* Depending on webpack "lifecycle" as everything is asynchronous.
* Normal factory module should execute first and provide a list of vendors
* that are used later when Html injections comes to order.
*
* Usage example:
* import 'vendor:../packages/dist/vendors.index.js';
* import 'vendor:../packages/dist/vendors.css';
*/
class VendorsInjectionPlugin {
apply(compiler) {
const vendorRgxp = /vendor:/;
const vendors = [];
// Ignore vendor files from webpack processing
new webpack.IgnorePlugin(vendorRgxp).apply(compiler);
// Handle import statements
compiler.hooks.normalModuleFactory.tap(VENDORS_INJECTION_PLUGIN, nmf => {
nmf.hooks.parser
.for('javascript/auto')
.tap(VENDORS_INJECTION_PLUGIN, parser => {
parser.hooks.import.tap(
VENDORS_INJECTION_PLUGIN,
(statement, source) => {
if (vendorRgxp.test(source)) {
// Register vendor import
const resource = source.split(':')[1];
vendors.push(resource);
// Remove vendor import statement from source
// Not sure how this is working. Taken from Webpack Harmony import plugin.
parser.state.module.addDependency(
new ConstDependency('', statement.range),
);
return true;
}
},
);
});
});
if (process.env.NODE_ENV === 'production') {
// In production libs don't split bundle thus
// no vendors needs to be injected
return;
}
// Inject vendors (chunks)
compiler.hooks.emit.tapPromise(VENDORS_INJECTION_PLUGIN, compilation => {
return new Promise(resolve => {
// "Subscribe" to HTML injections flow - place to add previously registered vendors
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
VENDORS_INJECTION_PLUGIN,
(data, callback) => {
Promise.all(
vendors.map(
resource =>
new Promise(resolve => {
fs.readFile(resource, (err, source) => {
const ext = resource.split('.').pop();
// Transform relative path to url safe path
const hashedResourcePath = `vendors/${hashString(
resource,
)}.${ext}`;
if (err) {
throw new Error(err);
}
compilation.assets[hashedResourcePath] = {
source: () => source,
size: () => source.length,
};
const assetSrc = `/${hashedResourcePath}`;
let vendorTagDesc;
switch (ext) {
case 'js':
vendorTagDesc = {
tagName: 'script',
voidTag: false,
attributes: { src: assetSrc },
};
break;
case 'css':
vendorTagDesc = {
tagName: 'link',
voidTag: false,
attributes: {
type: 'text/css',
href: assetSrc,
rel: 'stylesheet',
},
};
break;
default:
throw new Error('Unknown vendor type');
}
data.headTags.push(vendorTagDesc);
resolve();
});
}),
),
).then(() => callback(null, data));
},
);
resolve(compilation);
});
});
}
}
module.exports = VendorsInjectionPlugin;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment