-
Notifications
You must be signed in to change notification settings - Fork 67
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
Feature/esm views #1686
Feature/esm views #1686
Conversation
? JSON.parse(process.env.ESBUILD_TARGET_FACTORY) | ||
: 'es2015'; | ||
const isApp = process.env.MODULAR_IS_APP === 'true'; | ||
const isEsmView = !isApp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was originally going to ask:
Is it safe to rely on
process.env.MODULAR_IS_APP !== 'true'
here? For my reference... where do theMODULAR_IS_APP
/ESBUILD_TARGET_FACTORY
environment variables come from? Are they set by the user or by reading configuration from a file etc?
having read further down I see these are set by code in the build. Might it be useful to have an environment variable to define the output type here (ie: ESM vs compiled)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
having read further down I see these are set by code in the build. Might it be useful to have an environment variable to define the output type here (ie: ESM vs compiled)?
Not sure I understand, but if we are running Webpack we are sure that we're either building an app
or an esm-view
. Defining MODULAR_COMPILE_TARGET='esm' | 'compile' (for example) will be functionally equivalent (isApp = MODULAR_COMPILE_TARGET === 'compile'
) but maybe a bit more indirect to read?
@@ -135,6 +157,35 @@ module.exports = function (webpackEnv) { | |||
}; | |||
|
|||
const webpackConfig = { | |||
externals: isApp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As there are many app only configuration options here, might it be worth using a merge utility such as webpack-merge so the config can be:
const webpackConfig = merge(
defaultConfig(),
isApp && appConfig(),
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed to do this in another PR.
packages/modular-scripts/src/esbuild-scripts/plugins/rewriteDependenciesPlugin.ts
Show resolved
Hide resolved
Co-authored-by: Steve King <steve@mydev.co>
Co-authored-by: Steve King <steve@mydev.co>
const esbuildTargetFactory = process.env.ESBUILD_TARGET_FACTORY | ||
? JSON.parse(process.env.ESBUILD_TARGET_FACTORY) | ||
: 'es2015'; | ||
const isApp = process.env.MODULAR_IS_APP === 'true'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: IS_MODULAR_APP
? and also to bring it to lowercase before checking if it matches true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: IS_MODULAR_APP?
It sounds better, but we have this half-convention of prepending env variables passed to the Webpack configuration with MODULAR_
:
MODULAR_ROOT: modularRoot,
MODULAR_PACKAGE: target,
MODULAR_PACKAGE_NAME: targetName,
also to bring it to lowercase before checking if it matches true
It's passed only by our code, so we're pretty sure it's true
(unless JSON.stringify(boolean)
changes case), but if you think it's more hygienic I can do that.
: 'es2020'; | ||
|
||
const { externalDependencies } = process.env.MODULAR_PACKAGE_DEPS | ||
? JSON.parse(process.env.MODULAR_PACKAGE_DEPS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this throw an error if MODULAR_PACKAGE_DEPS
is anything other than an object or boolean? Can we be more defensive with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're only going to be using externalDependencies
, can we just have the dependencies in the root of the object instead of nesting it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see. This env var is generated at time of execution and passed in with no expectation of user configuration.
If that's the case, why doesn't the build script just fetch the external dependencies itself instead of reading it from the env var?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's the case, why doesn't the build script just fetch the external dependencies itself instead of reading it from the env var?
Because then we would need to either compile our getPackageDependencies.ts
into javascript at runtime for webpack.config.js
to use it, or "downgrade" it to js. There is no easy way for the webpack config to invoke our typescript utilities, and we use getPackageDependencies
for other things (possibly even ghost testing in the future).
The real problem here is that webpack.config.js
is not invoked by us, but by Webpack itself and we have to pass parameters to it via the environment, which is something we inherited from create-react-app.
I'm sure there's a way to solve this in a better way (ie generate a JSON configuration file from our build scripts), and I would like to do that, but that's for another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this script run in the same process as the script that generates the configuration? If so we could switch to using process.modular = { packageDependencies, isApp, ... }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this script run in the same process as the script that generates the configuration? If so we could switch to using process.modular = { packageDependencies, isApp, ... }
I'm not really sure what you mean, but this is not really a script we execute, it's the actual Webpack configuration (that can be a javascript file), executed by Webpack itself. What we do is a bit convoluted:
- In
build/index.ts
we call all our ts utilities, get the dependencies, set the environment variables and start a new javascript process in viaexeca
- the process is contained inreact-scripts/scripts/build.js
build.js
starts webpack, withconst compiler = webpack(configFactory('production'));
. This is taken directly from CRA.- The
webpack
library starts a Webpack process, which in turn executes the configuration atwebpack.config.js
(this file, which needs to be js since is directlyrequire
d by Webpack, afaik).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed to do this in another PR.
const string = ` | ||
import ReactDOM from 'react-dom' | ||
import React from 'react'; | ||
import Component from './src/index.tsx'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we template in paths.appIndexJs
in case the component is being exported out of an index.jsx
file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, done!
@@ -129,7 +130,7 @@ async function addPackage({ | |||
|
|||
const modularTemplateType = modularTemplatePackageJson?.modular | |||
?.templateType as string; | |||
if (!['app', 'view', 'package'].includes(modularTemplateType)) { | |||
if (!['app', 'esm-view', 'view', 'package'].includes(modularTemplateType)) { | |||
throw new Error( | |||
`${templateName} has modular type: ${modularTemplateType}, which does not exist, please use update this template`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To confirm, we're going to have to publish a modular-template-esm-view package as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already done in advance, we've been already burned by this :) -> https://www.npmjs.com/package/modular-scripts
); | ||
|
||
// Create and write trampoline file | ||
const trampolineBuildResult = await createViewTrampoline( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createViewTrampoline uses esbuild
which means it's not build system agnostic. How does this play with a standalone build run with USE_MODULAR_WEBPACK=true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We build the trampoline with esbuild
either way. The trampoline is not user code, we 100% control its template, so how it's built makes no difference to the user. We only build it to correctly replace its deps (react-dom
and react
) with the CDN template + allow / block list that the user has provided, via the esbuild plugin.
<html> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="static/js/_trampoline.js"></script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use the %PUBLIC_URL%
template to keep in line with the other index.html files we allow configuration for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Users are not supposed to provide their own index.html
for an esm-view
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they have "homepage" set in the package.json, we want to interpolate that into as the PUBLIC_URL when we build apps with webpack (https://github.com/jpmorganchase/modular/blob/main/packages/modular-scripts/react-scripts/config/webpack.config.js#L164-L167 ) . If the esm-view is meant to be able to be built as a standalone app, we should mirror this. It'll be important for building in CI if the assets are going to be nested or not stored in root.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll be important for building in CI if the assets are going to be nested or not stored in root.
The only assets that are allowed for a standalone esm-view
are cssEntryPoint
, jsEntryPoint
and the runtime index.js
if we're modular start
ing the application. We insert them dynamically - respecting the %PUBLIC_URL%
- in the createIndex
function just below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we set the PUBLIC_URL for the js and css entrypoints, why not also set them for the trampoline file?
<script type="module" src="%PUBLIC_URL%/static/js/_trampoline.js"></script>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we set the PUBLIC_URL for the js and css entrypoints, why not also set them for the trampoline file?
<script type="module" src="%PUBLIC_URL%/static/js/_trampoline.js"></script>
Ah, ok, I understand. This sparked a mini refactoring of api.ts, with common code in just one place and a better interface, plus obviously %PUBLIC_URL%
interpolation. Done.
@@ -50,6 +50,7 @@ async function addPackage({ | |||
choices: [ | |||
{ title: 'A plain package', value: 'package' }, | |||
{ title: 'A view within an application', value: 'view' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe to be clear what the difference between view and esm-view, could we update the title of view to indicate it's not an esm?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cangarugula Happy to change it. Not sure of how to word this though, "A non-ESM view within an application" sounds bad. Do you have suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll bring some more options to the table to help us think through what we're trying to convey.
The first and most concise is to simply say what these things are and require the reader to view the docs to understand what "view" and "app" mean.
- A plain package
- A view compiled to ES Modules
- A view compiled to a bundle file
- A standalone app
- Choose my own template
Following my previous logic, we could even shorten "A standalone app" to "An app".
However, if we wanted to be more descriptive, we could throw in an indication of how these things relate to micro-frontends, which is modular's thing:
- A plain package
- A micro-frontend view compiled to ES Modules
- A micro-frontend view compiled to a bundle file
- A micro-frontend container app
- Choose my own template
We can even shorten things a little bit:
- A plain package
- A micro-frontend view (ES Modules)
- A micro-frontend view (bundle file)
- A micro-frontend container app
- Choose my own template
What do people think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add the type
to better disambiguate?
- A plain package (package)
- A micro-frontend view compiled to ES Modules (esm-view)
- A micro-frontend view compiled to a bundle file (view)
- A micro-frontend container app (app)
- Choose my own template
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also add "react" in any of these titles or is that already obvious?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably just choose one and change it in the future if we think it's confusing. I'll start a poll later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cangarugula @benpryke and @steveukx chose Option C. I incorporated it and:
- Incorporated @benpryke's suggestion of not repeating the word "app"
- Incorporated @cangarugula's preference of specifying "react" for esm-views, views and apps
Introduce new type
esm-view
, which builds likeapp
(both webpack & esbuild), but:export
ing something (typically a React component)See the doc PR (WIP) for more detailed info: #1704