Skip to content

A hacky attempt at supporting createSharedEntry() in Webpack 4 #339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions fixtures/js/shared_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// used in a createdSharedEntry() test
require('./no_require');
require('./requires_arrow_function');
require('./../css/h1_style.css');
require('./print_to_app');
8 changes: 4 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,14 @@ class Encore {
}

/**
* Add a "commons" file that holds JS shared by multiple chunks.
* Add a "commons" file that holds JS shared by multiple chunks/files.
*
* @param {string} name The chunk name (e.g. vendor to create a vendor.js)
* @param {string|Array} files Array of files to put in the vendor entry
* @param {string} file A file whose code & imports should be put into the shared file.
* @returns {Encore}
*/
createSharedEntry(name, files) {
webpackConfig.createSharedEntry(name, files);
createSharedEntry(name, file) {
webpackConfig.createSharedEntry(name, file);

return this;
}
Expand Down
10 changes: 8 additions & 2 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class WebpackConfig {
this.publicPath = null;
this.manifestKeyPrefix = null;
this.sharedCommonsEntryName = null;
this.sharedCommonsEntryFile = null;
this.providedVariables = {};
this.configuredFilenames = {};
this.aliases = {};
Expand Down Expand Up @@ -317,15 +318,20 @@ class WebpackConfig {
this.splitChunksConfigurationCallback = callback;
}

createSharedEntry(name, files) {
createSharedEntry(name, file) {
// don't allow to call this twice
if (this.sharedCommonsEntryName) {
throw new Error('createSharedEntry() cannot be called multiple times: you can only create *one* shared entry.');
}

if (Array.isArray(file)) {
throw new Error('Argument 2 to createSharedEntry() must be a single string file: not an array of files. Try creating one file that requires/imports all the modules that should be included.');
}

this.sharedCommonsEntryName = name;
this.sharedCommonsEntryFile = file;

this.addEntry(name, files);
this.addEntry(name, file);
}

enablePostCssLoader(postCssLoaderOptionsCallback = () => {}) {
Expand Down
24 changes: 24 additions & 0 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ const vuePluginUtil = require('./plugins/vue');
const friendlyErrorPluginUtil = require('./plugins/friendly-errors');
const assetOutputDisplay = require('./plugins/asset-output-display');
const notifierPluginUtil = require('./plugins/notifier');
const sharedEntryConcatPuginUtil = require('./plugins/shared-entry-concat');
const PluginPriorities = require('./plugins/plugin-priorities');
const applyOptionsCallback = require('./utils/apply-options-callback');
const tmp = require('tmp');
const fs = require('fs');
const path = require('path');

class ConfigGenerator {
/**
Expand Down Expand Up @@ -114,6 +118,24 @@ class ConfigGenerator {
entry[entryName] = entryChunks;
}

if (this.webpackConfig.sharedCommonsEntryName) {
/*
* This is a hack: we need to create a new "entry"
* file that simply requires the same file that
* the "shared entry" requires.
*
* See shared-entry-concat-plugin.js for more details.
*/
const tmpFileObject = tmp.fileSync();
fs.writeFileSync(
tmpFileObject.name,
// quotes in the filename would cause problems
`require('${path.resolve(this.webpackConfig.getContext(), this.webpackConfig.sharedCommonsEntryFile)}')`
);

entry._tmp_shared = tmpFileObject.name;
}

return entry;
}

Expand Down Expand Up @@ -301,6 +323,8 @@ class ConfigGenerator {
assetOutputDisplay(plugins, this.webpackConfig, friendlyErrorPlugin);
}

sharedEntryConcatPuginUtil(plugins, this.webpackConfig);

this.webpackConfig.plugins.forEach(function(plugin) {
plugins.push(plugin);
});
Expand Down
1 change: 1 addition & 0 deletions lib/plugins/plugin-priorities.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
DeleteUnusedEntriesJSPlugin: 0,
EntryFilesManifestPlugin: 0,
WebpackManifestPlugin: 0,
SharedEntryContactPlugin: 0,
LoaderOptionsPlugin: 0,
ProvidePlugin: 0,
CleanWebpackPlugin: 0,
Expand Down
33 changes: 33 additions & 0 deletions lib/plugins/shared-entry-concat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const SharedEntryConcatPlugin = require('../webpack/shared-entry-concat-plugin');
const PluginPriorities = require('./plugin-priorities');
const path = require('path');

/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
if (!webpackConfig.sharedCommonsEntryName) {
return;
}

plugins.push({
plugin: new SharedEntryConcatPlugin(
webpackConfig.sharedCommonsEntryName,
webpackConfig.outputPath
),
priority: PluginPriorities.SharedEntryContactPlugin
});
};
68 changes: 68 additions & 0 deletions lib/webpack/shared-entry-concat-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const fs = require('fs');
const path = require('path');

function SharedEntryConcatPlugin(sharedEntryName, buildDir) {
this.sharedEntryName = sharedEntryName;
this.buildDir = buildDir;
}

SharedEntryConcatPlugin.prototype.apply = function(compiler) {
const done = (stats) => {
if (stats.hasErrors()) {
return;
}

/*
* This is a hack. See ConfigGenerator.buildEntryConfig()
* for other details.
*
* Basically, the "_tmp_shared" entry is created automatically
* as a "fake" entry. Internally, it simply requires the same
* file that is the source file of the shared entry.
*
* In this plugin, we literally read the final, compiled _tmp_shared.js
* entry, and put its contents at the bottom of the final, compiled,
* shared commons file. Then, we delete _tmp_shared.js. This
* is because the shared entry is actually "removed" as an entry
* file in SplitChunksPlugin, which means that if it contains
* any code that should be executed, that code is not normally
* executed. This fixes that.
*/

const sharedEntryOutputFile = path.join(this.buildDir, this.sharedEntryName + '.js');
const tmpEntryBootstrapFile = path.join(this.buildDir, '_tmp_shared.js');

if (!fs.existsSync(sharedEntryOutputFile)) {
throw new Error(`Could not find shared entry output file: ${sharedEntryOutputFile}`);
}

if (!fs.existsSync(tmpEntryBootstrapFile)) {
throw new Error(`Could not find temporary shared entry bootstrap file: ${tmpEntryBootstrapFile}`);
}

fs.writeFileSync(
sharedEntryOutputFile,
fs.readFileSync(sharedEntryOutputFile) + fs.readFileSync(tmpEntryBootstrapFile)
);

fs.unlinkSync(tmpEntryBootstrapFile);
};

compiler.hooks.done.tap(
{ name: 'SharedEntryConcatPlugin' },
done
);
};

module.exports = SharedEntryConcatPlugin;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"sinon": "^2.3.4",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"tmp": "^0.0.33",
"ts-loader": "^4.3.0",
"typescript": "^2.3.4",
"url-loader": "^1.0.1",
Expand Down
5 changes: 2 additions & 3 deletions test/functional.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ describe('Functional tests using webpack', function() {
config.setPublicPath('/build');
config.addEntry('main', ['./js/no_require', './js/code_splitting', './js/arrow_function', './js/print_to_app']);
config.addEntry('other', ['./js/no_require', './css/h1_style.css']);
config.createSharedEntry('shared', ['./js/no_require', './js/requires_arrow_function', './css/h1_style.css']);
config.createSharedEntry('shared', './js/shared_example');

testSetup.runWebpack(config, (webpackAssert) => {
// check the file is extracted correctly
Expand Down Expand Up @@ -650,10 +650,9 @@ describe('Functional tests using webpack', function() {
[
'build/runtime.js',
'build/shared.js',
'build/main.js'
],
(browser) => {
// assert that the javascript executed
// assert that the javascript brought into shared is executed
browser.assert.text('#app', 'Welcome to Encore!');
done();
}
Expand Down