Skip to content
This repository has been archived by the owner on Oct 1, 2024. It is now read-only.

Add appendToEntries to allow outside sources to add to the final entries object before compilation #1932

Merged
merged 1 commit into from
Jun 8, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,76 @@ import {basename, resolve} from 'path';
import globToRegExp from 'glob-to-regexp';
import {Compiler, Entry} from 'webpack';

type EntryOption = Compiler['options']['entry'];

export interface Options {
pattern: string;
folder: string | string[];
nameFromFile: (file: string) => string;
appendToEntries: {[entryName: string]: string};
}

type EntryOption = Compiler['options']['entry'];

/**
* A webpack plugin that automatically configures webpack entries by checking the filesystem
* @param config
* @param folder - string or array of strings to look for when auto detection of entries happens
* @param pattern - regex to be used to compare files that will be included as entries
* @param nameFromFile - a callback that provides the entry name based on file path for each file
* @param appendToEntries - a key/value object that will take name of the entry and an entry point string
* and would append these to the "magic" entries before return all entries to the
* webpack compiler:
*
* eg: {main: 'path/to/foo'}
*
* In the above example `path/to/foo` would be appended (only once) to `main`.
* The result would be:
*
* {
* main: ['./index.js', 'path/to/foo']
* }
*
* this is useful for things like Hot Module Reloading, where the entry for
* the client side code needs to be bundled with the main business logic to work
*
* @returns a customized webpack plugin
*
* # Static Helpers
* @function client - returns a webpack plugin preconfigured with defaults for the `env.target = client'
* @param folder
* @param appendToEntries
*
*
* @function server - returns a webpack plugin preconfigured with defaults for the `env.target = server'
* @param folder
* @param appendToEntries
*
*
*/
export class MagicEntriesPlugin {
static server({folder = '.'}: Partial<Pick<Options, 'folder'>> = {}) {
static server({
folder = '.',
appendToEntries = {},
}: Partial<Pick<Options, 'folder' | 'appendToEntries'>> = {}) {
return new MagicEntriesPlugin({
folder,
pattern: '*.entry.server.{jsx,js,ts,tsx}',
nameFromFile(file: string) {
return defaultNameFromFile(file).replace('.server', '');
},
appendToEntries,
});
}

static client({folder = '.'}: Partial<Pick<Options, 'folder'>> = {}) {
static client({
folder = '.',
appendToEntries = {},
}: Partial<Pick<Options, 'folder' | 'appendToEntries'>> = {}) {
return new MagicEntriesPlugin({
folder,
pattern: '*.entry.client.{jsx,js,ts,tsx}',
nameFromFile(file: string) {
return defaultNameFromFile(file).replace('.client', '');
},
appendToEntries,
});
}

Expand All @@ -44,11 +83,13 @@ export class MagicEntriesPlugin {
pattern = '*.entry.{jsx,js,ts,tsx}',
folder = '.',
nameFromFile = defaultNameFromFile,
appendToEntries = {},
}: Partial<Options> = {}) {
this.options = {
folder,
pattern,
nameFromFile,
appendToEntries,
};
this.compiledPattern = globToRegExp(pattern, {extended: true});
}
Expand All @@ -58,13 +99,11 @@ export class MagicEntriesPlugin {

compiler.options.entry = async () => {
const defaultEntries = await normalizedEntries(originalEntries);

const entries = {
...(await defaultEntries),
...(await this.autodetectEntries(compiler)),
};

return entries;
return this.appendToEntries(entries);
};
}

Expand Down Expand Up @@ -98,6 +137,38 @@ export class MagicEntriesPlugin {
return entries;
}
}

appendToEntries(entries: Entry): Entry {
if (this.options.appendToEntries) {
for (const [entryName, entry] of Object.entries(entries)) {
if (
this.options.appendToEntries[entryName] &&
Array.isArray(entries[entryName]) &&
!entries[entryName].includes(this.options.appendToEntries[entryName])
) {
entries[entryName] = [
...entry,
this.options.appendToEntries[entryName],
];
}

if (
this.options.appendToEntries[entryName] &&
typeof entries[entryName] === 'string' &&
entries[entryName].lastIndexOf(
this.options.appendToEntries[entryName],
) < 0
) {
entries[entryName] = [
entry as string,
this.options.appendToEntries[entryName],
];
}
}
return entries;
}
return entries;
}
}

function defaultNameFromFile(filename: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,46 +242,110 @@ describe('magic-entries-webpack-plugin', () => {
},
BUILD_TIMEOUT,
);
});

describe('virtual modules', () => {
it(
'creates a magic entrypoint for virtually created entrypoints',
async () => {
const clientWebpackConfig = {
...BASIC_WEBPACK_CONFIG,
plugins: [
new VirtualModulesPlugin({
'basic-js.entry.client.js': ENTRY_B,
'basic-jsx.entry.client.jsx': ENTRY_B,
'basic-ts.entry.client.ts': ENTRY_B,
'basic-tsx.entry.client.tsx': ENTRY_B,
}),
MagicEntriesPlugin.client(),
],
};
const name = 'node-virtual-module-entrypoints';
await withWorkspace(name, async ({workspace}) => {
await workspace.write('index.js', ENTRY_A);
await workspace.write('cats.js', CATS_MODULE);
const result = await runBuild(workspace, clientWebpackConfig);

const js = getModule(result.modules, 'basic-js').source;
expect(js).toBeDefined();
expect(js).toMatch(ENTRY_B);
const jsx = getModule(result.modules, 'basic-jsx').source;
expect(jsx).toBeDefined();
expect(jsx).toMatch(ENTRY_B);
const ts = getModule(result.modules, 'basic-ts').source;
expect(ts).toBeDefined();
expect(ts).toMatch(ENTRY_B);
const tsx = getModule(result.modules, 'basic-tsx').source;
expect(tsx).toBeDefined();
expect(tsx).toMatch(ENTRY_B);
});
describe('appendToEntries', () => {
it('appends to <foo> entry when entry is an array', () => {
const plugin = MagicEntriesPlugin.client({
appendToEntries: {
main: 'webpack-hotmiddleware/client',
},
BUILD_TIMEOUT,
});

const before = {main: ['./index.js']};
expect(before).toStrictEqual(
expect.objectContaining({main: ['./index.js']}),
);
const after = plugin.appendToEntries(before);
expect(after).toStrictEqual({
main: ['./index.js', 'webpack-hotmiddleware/client'],
});
});

it('appends to <foo> entry when entry is a string', () => {
const plugin = MagicEntriesPlugin.client({
appendToEntries: {
main: 'webpack-hotmiddleware/client',
},
});

const before = {main: './index.js'};
expect(before).toStrictEqual(
expect.objectContaining({main: './index.js'}),
);
const after = plugin.appendToEntries(before);
expect(after).toStrictEqual({
main: ['./index.js', 'webpack-hotmiddleware/client'],
});
});

it('skips adding duplicate entries when entry is an array', () => {
const plugin = MagicEntriesPlugin.client({
appendToEntries: {
main: 'webpack-hotmiddleware/client',
},
});

const before = {main: ['./index.js', 'webpack-hotmiddleware/client']};
const after = plugin.appendToEntries(before);
expect(after).toStrictEqual({
main: ['./index.js', 'webpack-hotmiddleware/client'],
});
});

it('skips adding duplicate entries when entry is string', () => {
const plugin = MagicEntriesPlugin.client({
appendToEntries: {
main: 'webpack-hotmiddleware/client',
},
});

const before = {main: 'webpack-hotmiddleware/client'};
const after = plugin.appendToEntries(before);
expect(after).toStrictEqual({
main: 'webpack-hotmiddleware/client',
});
});
});

describe('virtual modules', () => {
it(
'creates a magic entrypoint for virtually created entrypoints',
async () => {
const clientWebpackConfig = {
...BASIC_WEBPACK_CONFIG,
plugins: [
new VirtualModulesPlugin({
'basic-js.entry.client.js': ENTRY_B,
'basic-jsx.entry.client.jsx': ENTRY_B,
'basic-ts.entry.client.ts': ENTRY_B,
'basic-tsx.entry.client.tsx': ENTRY_B,
}),
MagicEntriesPlugin.client(),
],
};
const name = 'node-virtual-module-entrypoints';
await withWorkspace(name, async ({workspace}) => {
await workspace.write('index.js', ENTRY_A);
await workspace.write('cats.js', CATS_MODULE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐱 🐈

const result = await runBuild(workspace, clientWebpackConfig);

const js = getModule(result.modules, 'basic-js').source;
expect(js).toBeDefined();
expect(js).toMatch(ENTRY_B);
const jsx = getModule(result.modules, 'basic-jsx').source;
expect(jsx).toBeDefined();
expect(jsx).toMatch(ENTRY_B);
const ts = getModule(result.modules, 'basic-ts').source;
expect(ts).toBeDefined();
expect(ts).toMatch(ENTRY_B);
const tsx = getModule(result.modules, 'basic-tsx').source;
expect(tsx).toBeDefined();
expect(tsx).toMatch(ENTRY_B);
});
},
BUILD_TIMEOUT,
);
});
});

Expand Down