-
Notifications
You must be signed in to change notification settings - Fork 2
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
Use Node.js-native ECMAScript modules #222
Conversation
external: [ | ||
'classnames', | ||
'express', | ||
'express-handlebars', | ||
'express-session', | ||
'morgan', | ||
'node:http', | ||
'node:path', | ||
'node:url', | ||
'prop-types', | ||
'react', | ||
'react-bootstrap-typeahead', | ||
'react-dom/server', | ||
'react-helmet', | ||
'react-redux', | ||
'react-router-dom', | ||
'react-router-dom/server.js', | ||
'redux', | ||
'redux-thunk' | ||
], |
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.
This is to omit the named modules from the bundle and only include them at runtime, as detailed here: https://rollupjs.org/troubleshooting/#warning-treating-module-as-external-dependency.
It also prevents (!) Unresolved dependencies
warnings in the build process:
src/server/app.js → built/main.js...
(!) Unresolved dependencies
https://rollupjs.org/troubleshooting/#warning-treating-module-as-external-dependency
node:http (imported by "src/server/app.js")
node:path (imported by "src/server/app.js")
node:url (imported by "src/server/app.js")
express (imported by "src/server/app.js", "src/server/api-router.js" and "src/server/router.js")
express-handlebars (imported by "src/server/app.js")
express-session (imported by "src/server/app.js")
morgan (imported by "src/server/app.js")
react-helmet (imported by "src/server/router.js", "src/react/Layout.jsx", "src/react/components/ErrorMessage.jsx" and "src/react/components/InstanceDocumentTitle.jsx")
react-router-dom (imported by "src/server/router.js", "src/react/AppRoutes.jsx", "src/react/Layout.jsx", "src/react/components/Header.jsx", "src/react/components/Navigation.jsx" and "src/react/components/SearchBar.jsx")
redux (imported by "src/server/router.js")
redux-thunk (imported by "src/server/router.js")
react (imported by "src/react/react-html.jsx", "src/react/AppRoutes.jsx", "src/react/pages/NotFound.jsx", "src/react/pages/Home.jsx", "src/react/Layout.jsx", "src/react/pages/instances/FestivalSeries.jsx", "src/react/pages/instances/Festival.jsx", "src/react/pages/instances/Person.jsx", "src/react/pages/lists/Companies.jsx", "src/react/pages/lists/Materials.jsx", "src/react/pages/instances/Award.jsx", "src/react/pages/instances/Venue.jsx", "src/react/pages/instances/Company.jsx", "src/react/pages/lists/People.jsx", "src/react/pages/lists/Productions.jsx", "src/react/pages/instances/AwardCeremony.jsx", "src/react/pages/lists/Venues.jsx", "src/react/pages/lists/Characters.jsx", "src/react/pages/lists/Seasons.jsx", "src/react/pages/lists/Awards.jsx", "src/react/pages/instances/Material.jsx", "src/react/pages/lists/Festivals.jsx", "src/react/pages/lists/AwardCeremonies.jsx", "src/react/pages/instances/Character.jsx", "src/react/pages/instances/Season.jsx", "src/react/pages/lists/FestivalSerieses.jsx", "src/react/pages/instances/Production.jsx", "src/react/components/Header.jsx", "src/react/components/FormattedJson.jsx", "src/react/components/ErrorMessage.jsx", "src/react/components/InstanceLabel.jsx", "src/react/components/Footer.jsx", "src/react/components/Navigation.jsx", "src/react/components/Notification.jsx", "src/react/components/InstanceDocumentTitle.jsx", "src/react/wrappers/ListWrapper.jsx", "src/react/components/PageTitle.jsx", "src/react/components/instance-forms/CompanyForm.jsx", "src/react/components/instance-forms/FestivalForm.jsx", "src/react/components/withInstancePageTitle.jsx", "src/react/components/instance-forms/FestivalSeriesForm.jsx", "src/react/components/instance-forms/AwardForm.jsx", "src/react/wrappers/InstanceWrapper.jsx", "src/react/components/ScrollToTop.jsx", "src/react/components/instance-forms/VenueForm.jsx", "src/react/components/SearchBar.jsx", "src/react/components/instance-forms/CharacterForm.jsx", "src/react/components/instance-forms/PersonForm.jsx", "src/react/components/instance-forms/AwardCeremonyForm.jsx", "src/react/components/instance-forms/SeasonForm.jsx", "src/react/components/instance-forms/ProductionForm.jsx", "src/react/components/instance-forms/MaterialForm.jsx", "src/react/components/form/ArrayItemActionButton.jsx", "src/react/components/form/InputAndErrors.jsx", "src/react/components/form/InputErrors.jsx", "src/react/components/form/FieldsetComponent.jsx", "src/react/components/form/Input.jsx", "src/react/components/form/FormWrapper.jsx" and "src/react/components/form/Fieldset.jsx")
react-dom/server (imported by "src/react/react-html.jsx")
react-redux (imported by "src/react/react-html.jsx", "src/react/Layout.jsx", "src/react/pages/instances/FestivalSeries.jsx", "src/react/pages/instances/Festival.jsx", "src/react/pages/instances/Person.jsx", "src/react/pages/lists/Companies.jsx", "src/react/pages/lists/Materials.jsx", "src/react/pages/instances/Award.jsx", "src/react/pages/instances/Venue.jsx", "src/react/pages/instances/Company.jsx", "src/react/pages/lists/People.jsx", "src/react/pages/lists/Productions.jsx", "src/react/pages/instances/AwardCeremony.jsx", "src/react/pages/lists/Venues.jsx", "src/react/pages/lists/Characters.jsx", "src/react/pages/lists/Seasons.jsx", "src/react/pages/lists/Awards.jsx", "src/react/pages/instances/Material.jsx", "src/react/pages/lists/Festivals.jsx", "src/react/pages/lists/AwardCeremonies.jsx", "src/react/pages/instances/Character.jsx", "src/react/pages/instances/Season.jsx", "src/react/pages/lists/FestivalSerieses.jsx", "src/react/pages/instances/Production.jsx" and "src/react/components/form/FormWrapper.jsx")
react-router-dom/server.js (imported by "src/react/react-html.jsx")
prop-types (imported by "src/react/Layout.jsx", "src/react/pages/instances/FestivalSeries.jsx", "src/react/pages/instances/Festival.jsx", "src/react/pages/instances/Person.jsx", "src/react/pages/lists/Companies.jsx", "src/react/pages/lists/Materials.jsx", "src/react/pages/instances/Award.jsx", "src/react/pages/instances/Venue.jsx", "src/react/pages/instances/Company.jsx", "src/react/pages/lists/People.jsx", "src/react/pages/lists/Productions.jsx", "src/react/pages/instances/AwardCeremony.jsx", "src/react/pages/lists/Venues.jsx", "src/react/pages/lists/Characters.jsx", "src/react/pages/lists/Seasons.jsx", "src/react/pages/lists/Awards.jsx", "src/react/pages/instances/Material.jsx", "src/react/pages/lists/Festivals.jsx", "src/react/pages/lists/AwardCeremonies.jsx", "src/react/pages/instances/Character.jsx", "src/react/pages/instances/Season.jsx", "src/react/pages/lists/FestivalSerieses.jsx", "src/react/pages/instances/Production.jsx", "src/react/components/FormattedJson.jsx", "src/react/components/ErrorMessage.jsx", "src/react/components/InstanceLabel.jsx", "src/react/components/Notification.jsx", "src/react/components/InstanceDocumentTitle.jsx", "src/react/wrappers/ListWrapper.jsx", "src/react/components/PageTitle.jsx", "src/react/components/instance-forms/CompanyForm.jsx", "src/react/components/instance-forms/FestivalForm.jsx", "src/react/components/withInstancePageTitle.jsx", "src/react/components/instance-forms/FestivalSeriesForm.jsx", "src/react/components/instance-forms/AwardForm.jsx", "src/react/wrappers/InstanceWrapper.jsx", "src/react/components/instance-forms/VenueForm.jsx", "src/react/components/instance-forms/CharacterForm.jsx", "src/react/components/instance-forms/PersonForm.jsx", "src/react/components/instance-forms/AwardCeremonyForm.jsx", "src/react/components/instance-forms/SeasonForm.jsx", "src/react/components/instance-forms/ProductionForm.jsx", "src/react/components/instance-forms/MaterialForm.jsx", "src/react/components/form/ArrayItemActionButton.jsx", "src/react/components/form/InputAndErrors.jsx", "src/react/components/form/InputErrors.jsx", "src/react/components/form/FieldsetComponent.jsx", "src/react/components/form/Input.jsx", "src/react/components/form/FormWrapper.jsx" and "src/react/components/form/Fieldset.jsx")
classnames (imported by "src/react/components/Notification.jsx", "src/react/components/PageTitle.jsx", "src/react/components/form/FieldsetComponent.jsx" and "src/react/components/form/Input.jsx")
react-bootstrap-typeahead (imported by "src/react/components/SearchBar.jsx")
watch: { | ||
clearScreen: false | ||
}, |
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.
Whether to clear the screen when a rebuild is triggered.
Ref. https://rollupjs.org/configuration-options/#watch-clearscreen
const clientScriptsBundle = { | ||
input: 'src/react/client-mount.jsx', | ||
output: { | ||
file: 'public/main.js', | ||
format: 'iife' | ||
}, | ||
watch: { | ||
clearScreen: false | ||
}, | ||
plugins: [ | ||
nodeResolve({ | ||
browser: true, | ||
extensions: ['.js', '.jsx'] | ||
}), | ||
babel({ | ||
babelHelpers: 'bundled', | ||
presets: ['@babel/preset-react'], | ||
extensions: ['.js', '.jsx'] | ||
}), | ||
commonjs(), | ||
replace({ | ||
preventAssignment: false, | ||
'process.env.NODE_ENV': JSON.stringify('development') | ||
}) | ||
] | ||
}; |
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.
This configuration is taken from https://www.codeguage.com/blog/setup-rollup-for-react#Configuring_Rollup.
@import 'react-bootstrap-typeahead/css/Typeahead.css'; | ||
@import 'react-bootstrap-typeahead/css/Typeahead.bs5.css'; | ||
@import 'react-bootstrap-typeahead/css/Typeahead'; | ||
@import 'react-bootstrap-typeahead/css/Typeahead.bs5'; |
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.
Without these changes, these imported files will be omitted from the transpiled CSS file.
applyMiddleware(...[thunkMiddleware]) | ||
applyMiddleware(...[thunkMiddleware.default]) |
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.
Without this change, the following error will occur when the app is run:
/{path}/dramatis-cms/node_modules/redux/lib/redux.js:162
return enhancer(createStore)(reducer, preloadedState);
^
TypeError: middleware is not a function
at /{path}/dramatis-cms/node_modules/redux/lib/redux.js:703:16
at Array.map (<anonymous>)
at /{path}/dramatis-cms/node_modules/redux/lib/redux.js:702:31
at createStore (/{path}/dramatis-cms/node_modules/redux/lib/redux.js:162:33)
at file:///{path}/dramatis-cms/src/server/router.js:13:15
at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
at ModuleLoader.import (node:internal/modules/esm/loader:475:24)
at asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:109:5)
Failed running 'built/main.js'
ded76e2
to
743488b
Compare
rollup.config.js
Outdated
esbuild({ | ||
jsxFactory: 'React.createElement', | ||
jsxFragment: 'React.Fragment' | ||
}) |
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.
The properties for the object provided as an argument are taken from this usage example https://github.com/egoist/rollup-plugin-esbuild#usage, though for many properties the default value is appropriate.
import esbuild from 'rollup-plugin-esbuild'
export default {
plugins: [
esbuild({
// All options are optional
include: /\.[jt]sx?$/, // default, inferred from `loaders` option
exclude: /node_modules/, // default
sourceMap: true, // default
minify: process.env.NODE_ENV === 'production',
target: 'es2017', // default, or 'es20XX', 'esnext'
jsx: 'transform', // default, or 'preserve'
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
// Like @rollup/plugin-replace
define: {
__VERSION__: '"x.y.z"',
},
tsconfig: 'tsconfig.json', // default
// Add extra loaders
loaders: {
// Add .json files support
// require @rollup/plugin-commonjs
'.json': 'json',
// Enable JSX in .js files too
'.js': 'jsx',
},
}),
],
}
743488b
to
338ab78
Compare
"build": "webpack", | ||
"watch": "webpack --watch", | ||
"build": "rollup --config", | ||
"watch": "rollup --config --watch", |
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.
watch
command requires --config
flag prior to --watch
flag.
Ref. Medium: The Ultimate Guide to Getting Started with the Rollup.js JavaScript Bundler by Craig Buckler
338ab78
to
fa331dd
Compare
fa331dd
to
47e29eb
Compare
This PR updates the codebase to use Node.js-native ECMAScript modules.
The decision to make this change now resulted from performing a periodic upgrade of dependencies to the latest version and discovering that the latest version of some packages now require ECMAScript modules, e.g. https://github.com/chaijs/chai/releases/tag/v5.0.0:
react-redux, redux, and redux-thunk also seem similarly affected.
There is a choice to remain on the currently installed version of packages like this, though it is inevitable that support will eventually stop for those versions and that upgrading will become necessary.
Codebase changes
The key change is to add to
package.json
:+ "type": "module"
All import statements for native module dependencies now require the file extension, e.g.
Import statements whose path pointed to a directory and relying on the default lookup of an
index.js
file now require that file (and its file extension) explicitly declared, e.g.Transpilation
Once the codebase has been changed to use ECMAScript modules, trying to run the app while there is still the presence of files with a
.jsx
extension will result in:And switching those files' extension from
jsx
to.js
will then result in the following error when it encounters the first piece of JSX syntax:While JSX files are present it is necessary to transpile the code.
Ref. Stack Overflow: Cannot use JSX with nodejs ESM module loader
Ref. facbook.github.io: JSX
This PR switches out Webpack for Rollup, an ECMAScript modules-first module bundler.
Consequently, all Babel-related and Webpack-related packages and code are removed (except for the introduction of the @rollup/plugin-babel package).
The following additional changes are then required:
CSS import statements whose path pointed to a file from an external package now require that the file extension is removed, e.g.
The thunk-middleware package requires that its default export is referenced explicitly:
Functionality checks
Before:
try
/catch
block, e.g.dramatis-cms/src/server/router.js
Line 20 in 555897d
try
/catch
block, e.g.dramatis-cms/src/server/router.js
Line 48 in 555897d
src/server/router.js
— triggers rebuild and preserves outputsrc/react/pages/instances/Festival.jsx
— triggers rebuild and preserves outputsrc/client/stylesheets/_navigation.scss
— triggers rebuild but does not preserve outputAfter:
try
/catch
block, e.g.dramatis-cms/src/server/router.js
Line 20 in 555897d
try
/catch
block, e.g.dramatis-cms/src/server/router.js
Line 48 in 555897d
src/server/router.js
— triggers rebuild and preserves outputsrc/react/pages/instances/Festival.jsx
— triggers rebuild but does not preserve output (will address preserving output in subsequent branch)src/client/stylesheets/_navigation.scss
— triggers rebuild but does not preserve output (same as existing state; will address preserving output in subsequent branch)package.json
start
command including multiple--watch-path
arguments which both change within a very short timeframe; update: the solution is to include the--watch-preserve-output
flag after each--watch-path
flag (rather than a single--watch-preserve-output
flag after both the--watch-path
flags, as this PR has implemented) — this fix will be applied in a subsequent PR (here: Preserve output for both watched paths #224)References:
New dev dependencies: