Skip to content

Commit

Permalink
feat(prerender): server-side only bundle modules w/ .server directory
Browse files Browse the repository at this point in the history
Any modules within a directory that ends with ".server" will not be bundled into browser builds, but ".server" modules will get included within the dist-hydrate-script output target. Additionally, any external module referenced from a ".server" module will not be bundled within the hydrate output target. For example, lodash or moment would not get bundled if they were only referenced from within "src/data.server/index.ts", but instead they'll be traditional nodejs require() imports.
  • Loading branch information
adamdbradley committed Sep 16, 2020
1 parent 4d49c63 commit d8fcb60
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 10 deletions.
27 changes: 23 additions & 4 deletions src/compiler/bundle/bundle-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ import { pluginHelper } from './plugin-helper';
import { resolveIdWithTypeScript, typescriptPlugin } from './typescript-plugin';
import { rollupCommonjsPlugin, rollupJsonPlugin, rollupNodeResolvePlugin, rollupReplacePlugin } from '@compiler-deps';
import { RollupOptions, TreeshakingOptions, rollup } from 'rollup';
import { serverPlugin } from './server-plugin';
import { userIndexPlugin } from './user-index-plugin';
import { workerPlugin } from './worker-plugin';

export const bundleOutput = async (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx, bundleOpts: BundleOptions) => {
export const bundleOutput = async (
config: d.Config,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
bundleOpts: BundleOptions,
) => {
try {
const rollupOptions = getRollupOptions(config, compilerCtx, buildCtx, bundleOpts);
const rollupBuild = await rollup(rollupOptions);
Expand All @@ -32,8 +38,20 @@ export const bundleOutput = async (config: d.Config, compilerCtx: d.CompilerCtx,
return undefined;
};

export const getRollupOptions = (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx, bundleOpts: BundleOptions) => {
const customResolveOptions = createCustomResolverAsync(config.sys, compilerCtx.fs, ['.tsx', '.ts', '.js', '.mjs', '.json', '.d.ts']);
export const getRollupOptions = (
config: d.Config,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
bundleOpts: BundleOptions,
) => {
const customResolveOptions = createCustomResolverAsync(config.sys, compilerCtx.fs, [
'.tsx',
'.ts',
'.js',
'.mjs',
'.json',
'.d.ts',
]);
const nodeResolvePlugin = rollupNodeResolvePlugin({
mainFields: ['collection:main', 'jsnext:main', 'es2017', 'es2015', 'module', 'main'],
customResolveOptions,
Expand Down Expand Up @@ -78,6 +96,7 @@ export const getRollupOptions = (config: d.Config, compilerCtx: d.CompilerCtx, b
extFormatPlugin(config),
extTransformsPlugin(config, compilerCtx, buildCtx, bundleOpts),
workerPlugin(config, compilerCtx, buildCtx, bundleOpts.platform, !!bundleOpts.inlineWorkers),
serverPlugin(config, bundleOpts.platform),
...beforePlugins,
nodeResolvePlugin,
resolveIdWithTypeScript(config, compilerCtx),
Expand All @@ -88,7 +107,7 @@ export const getRollupOptions = (config: d.Config, compilerCtx: d.CompilerCtx, b
...config.commonjs,
}),
...afterPlugins,
pluginHelper(config, buildCtx),
pluginHelper(config, buildCtx, bundleOpts.platform),
rollupJsonPlugin({
preferConst: true,
}),
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/bundle/plugin-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type * as d from '../../declarations';
import { buildError } from '@utils';
import { relative } from 'path';

export const pluginHelper = (config: d.Config, builtCtx: d.BuildCtx) => {
export const pluginHelper = (config: d.Config, builtCtx: d.BuildCtx, platform: string) => {
return {
name: 'pluginHelper',
resolveId(importee: string, importer: string): null {
Expand All @@ -22,7 +22,7 @@ export const pluginHelper = (config: d.Config, builtCtx: d.BuildCtx) => {
}
const diagnostic = buildError(builtCtx.diagnostics);
diagnostic.header = `Node Polyfills Required`;
diagnostic.messageText = `For the import "${importee}" to be bundled${fromMsg}, ensure the "rollup-plugin-node-polyfills" plugin is installed and added to the stencil config plugins. Please see the bundling docs for more information.
diagnostic.messageText = `For the import "${importee}" to be bundled${fromMsg}, ensure the "rollup-plugin-node-polyfills" plugin is installed and added to the stencil config plugins (${platform}). Please see the bundling docs for more information.
Further information: https://stenciljs.com/docs/module-bundling`;
}
return null;
Expand Down
64 changes: 64 additions & 0 deletions src/compiler/bundle/server-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type * as d from '../../declarations';
import { isString, normalizeFsPath } from '@utils';
import type { Plugin } from 'rollup';
import { isOutputTargetHydrate } from '../output-targets/output-utils';
import { isAbsolute } from 'path';

export const serverPlugin = (config: d.Config, platform: string): Plugin => {
const isHydrateBundle = platform === 'hydrate';
const serverVarid = `@removed-server-code`;

const isServerOnlyModule = (id: string) => {
if (isString(id)) {
id = normalizeFsPath(id);
return id.includes('.server/') || id.endsWith('.server');
}
return false;
};

const externals = isHydrateBundle ? config.outputTargets.filter(isOutputTargetHydrate).flatMap(o => o.external) : [];

return {
name: 'serverPlugin',

resolveId(id, importer) {
if (id === serverVarid) {
return id;
}
if (isHydrateBundle) {
if (externals.includes(id)) {
// don't attempt to bundle node builtins for the hydrate bundle
return {
id,
external: true,
};
}
if (isServerOnlyModule(importer) && !id.startsWith('.') && !isAbsolute(id)) {
// do not bundle if the importer is a server-only module
// and the module it is importing is a node module
return {
id,
external: true,
};
}
} else {
if (isServerOnlyModule(id)) {
// any path that has .server in it shouldn't actually
// be bundled in the web build, only the hydrate build
return serverVarid;
}
}
return null;
},

load(id) {
if (id === serverVarid) {
return {
code: 'export default {};',
syntheticNamedExports: true,
};
}
return null;
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydr
var close = () => {};
var confirm = $stencilWindow.confirm.bind($stencilWindow);
var dispatchEvent = $stencilWindow.dispatchEvent.bind($stencilWindow);
var fetch = $stencilWindow.fetch.bind($stencilWindow);
var focus = $stencilWindow.focus.bind($stencilWindow);
var getComputedStyle = $stencilWindow.getComputedStyle.bind($stencilWindow);
var matchMedia = $stencilWindow.matchMedia.bind($stencilWindow);
Expand All @@ -37,6 +38,7 @@ export function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydr
var DOMTokenList = $stencilWindow.DOMTokenList;
var Element = $stencilWindow.Element;
var Event = $stencilWindow.Event;
var FetchError = $stencilWindow.FetchError;
var Headers = $stencilWindow.Headers;
var HTMLAnchorElement = $stencilWindow.HTMLAnchorElement;
var HTMLBaseElement = $stencilWindow.HTMLBaseElement;
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/output-targets/dist-hydrate-script/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { generateHydrateApp } from './generate-hydrate-app';
import { isOutputTargetHydrate } from '../output-utils';

export const outputHydrateScript = async (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) => {
if (!config.buildDist) {
return;
}

const hydrateOutputTargets = config.outputTargets.filter(isOutputTargetHydrate);
if (hydrateOutputTargets.length > 0) {
const timespan = buildCtx.createTimeSpan(`generate hydrate app started`);
Expand Down

0 comments on commit d8fcb60

Please sign in to comment.