Skip to content
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

feat: add moduleFederation option #938

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
6 changes: 4 additions & 2 deletions packages/babel-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const properties = [

const LOADABLE_COMMENT = '#__LOADABLE__'

const loadablePlugin = declare((api, { defaultImportSpecifier = 'loadable' }) => {
const loadablePlugin = declare((api, babelOptions) => {
const { defaultImportSpecifier = 'loadable' } = babelOptions

const { types: t } = api

function collectImportCallPaths(startPath) {
Expand All @@ -33,7 +35,7 @@ const loadablePlugin = declare((api, { defaultImportSpecifier = 'loadable' }) =>
return imports
}

const propertyFactories = properties.map(init => init(api))
const propertyFactories = properties.map(init => init(api, babelOptions))

function isValidIdentifier(path, loadableImportSpecifier, lazyImportSpecifier) {
// `loadable()`
Expand Down
61 changes: 50 additions & 11 deletions packages/babel-plugin/src/properties/resolve.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { getImportArg } from '../util'

export default function resolveProperty({ types: t, template }) {
const buildStatements = template`
if (require.resolveWeak) {
return require.resolveWeak(ID)
}

return eval('require.resolve')(ID)
`
export default function resolveProperty(
{ types: t, template },
{ moduleFederation },
) {
const templates = {
federated: template`
require(ID)

if (require.resolveWeak) {
return require.resolveWeak(ID)
}

return eval('require.resolve')(ID)
`,
standard: template`
if (require.resolveWeak) {
return require.resolveWeak(ID)
}

return eval('require.resolve')(ID)
`,
}

function getCallValue(callPath) {
const importArg = getImportArg(callPath)
Expand All @@ -27,11 +41,36 @@ export default function resolveProperty({ types: t, template }) {
return t.stringLiteral(importArg.node.value)
}

return ({ callPath, funcPath }) =>
t.objectMethod(
return ({ callPath, funcPath }) => {
const { isBrowser } = process

if (moduleFederation) {
if ('isBrowser' in process) {
if (isBrowser === true) {
// eslint-disable-next-line no-console
console.warn(
'The "moduleFederation: true" option will disable code splitting on the client-side',
)
}
} else {
// eslint-disable-next-line no-console
console.warn(
'It\'s recommended to use "isBrowser" global variable to detect the environment\n' +
'Try to add "webpack.DefinePlugin({ \'process.isBrowser\': true })" to your webpack config',
)
Copy link

Choose a reason for hiding this comment

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

Is there something internal to a "client side" Webpack target that can be read automatically? For example, as a developer if I set target: "web" (the default) or another client-targetted value, then this plugin should probably just error out here (or have a "force" option to override).

Or does Webpack not provide a standard way to expose "is browser" and every webpack tool has to invent their own? (like here)

Copy link
Collaborator

Choose a reason for hiding this comment

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

One can use package.json:browser field to detect browser target - https://github.com/theKashey/detect-node/blob/master/package.json#L6-L10
But this is a run-time operation.

Copy link
Author

Choose a reason for hiding this comment

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

These solutions only work during runtime, @theKashey do you know if webpack provides some context to babel during the compilation?

Copy link
Collaborator

Choose a reason for hiding this comment

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

According to babel-loader source - no, it does not.

Copy link

Choose a reason for hiding this comment

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

Hm, this begs the question then: can this be an argument to LoadablePlugin webpack plugin instead, and then read somehow by the Babel plugin? Is that possible?

That would alleviate most concerns of misuse, as it could be documented to just use this option for your client-side Webpack build, which is something already natural to loadable components.

With a Babel plugin option, users have to implement some logic to modify their Babel options dynamically based on whether it's being used by the Webpack server build or client build, which while not weird is an additional configuration workflow added to use loadable, and they also have to implement some DefinePlugin thing, which again is another workflow change.

No big deal, just wondering if that could simplify things

Copy link
Collaborator

Choose a reason for hiding this comment

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

🤔 BINGO!
LoadablePlugin can automatically call DefinePlugin with the variables required. As webpack plugin is expected to be used only for client side it can secure dead code elimination (in production only) and other checks to guide proper configuration.

Copy link
Author

Choose a reason for hiding this comment

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

Awesome @theKashey! I will work on that here! 🚀

Copy link
Author

Choose a reason for hiding this comment

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

@theKashey added DefinePlugin to LoadablePlugin and updated the warning messages

}
}

const targetTemplate =
moduleFederation && isBrowser === false ? 'federated' : 'standard'

return t.objectMethod(
'method',
t.identifier('resolve'),
funcPath.node.params,
t.blockStatement(buildStatements({ ID: getCallValue(callPath) })),
t.blockStatement(
templates[targetTemplate]({ ID: getCallValue(callPath) }),
),
)
}
}