From 04ece0a034ff88eedd06268f5dc0469226600a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9di-R=C3=A9mi=20Hashim?= Date: Thu, 17 Feb 2022 16:34:06 +0000 Subject: [PATCH 01/35] feat(cli): support plugin entrypoint when validating entrypoints --- cli/src/lib/compiler/entrypoints.js | 32 ++++- cli/src/lib/compiler/entrypoints.test.js | 162 +++++++++++++++++++++++ 2 files changed, 188 insertions(+), 6 deletions(-) diff --git a/cli/src/lib/compiler/entrypoints.js b/cli/src/lib/compiler/entrypoints.js index 69314fd68..6b245ca4d 100644 --- a/cli/src/lib/compiler/entrypoints.js +++ b/cli/src/lib/compiler/entrypoints.js @@ -27,15 +27,35 @@ exports.verifyEntrypoints = ({ resolveModule = require.resolve, }) => { if (config.type === 'app') { - verifyEntrypoint({ - entrypoint: config.entryPoints.app, - basePath: paths.base, - resolveModule, - }) + if ( + !config.entryPoints || + (!config.entryPoints.app && !config.entryPoints.plugin) + ) { + throw new Error('Apps must define an app or plugin entrypoint') + } + + if (config.entryPoints.app) { + verifyEntrypoint({ + entrypoint: config.entryPoints.app, + basePath: paths.base, + resolveModule, + }) + } + if (config.entryPoints.plugin) { + verifyEntrypoint({ + entrypoint: config.entryPoints.plugin, + basePath: paths.base, + resolveModule, + }) + } return } - const verifyLibraryEntrypoint = (entrypoint) => { + if (!config.entryPoints || !config.entryPoints.lib) { + throw new Error('Libraries must define a lib entrypoint') + } + + const verifyLibraryEntrypoint = entrypoint => { switch (typeof entrypoint) { case 'string': verifyEntrypoint({ diff --git a/cli/src/lib/compiler/entrypoints.test.js b/cli/src/lib/compiler/entrypoints.test.js index b70a938cb..7a7670fa4 100644 --- a/cli/src/lib/compiler/entrypoints.test.js +++ b/cli/src/lib/compiler/entrypoints.test.js @@ -256,4 +256,166 @@ describe('verifyEntrypoints', () => { ) expect(resolveModule).toHaveBeenCalledTimes(3) }) + + it(`does not throw an error if a plugin's entrypoint is located in the 'src' directory`, () => { + const resolveModule = jest.fn() + + const appConfig1 = { + type: 'app', + entryPoints: { + plugin: './src/plugin.js', + }, + } + expect(() => + verifyEntrypoints({ + config: appConfig1, + paths: { + base: '/the/base/path', + }, + resolveModule, + }) + ).not.toThrow() + + const appConfig2 = { + type: 'app', + entryPoints: { + plugin: 'src/plugin.js', + }, + } + expect(() => + verifyEntrypoints({ + config: appConfig2, + paths: { + base: '/the/base/path', + }, + resolveModule, + }) + ).not.toThrow() + }) + + it(`throws an error if a plugin's entrypoint is not located in the 'src' directory`, () => { + const appConfig = { + type: 'app', + entryPoints: { + plugin: 'plugin.js', + }, + } + expect(() => + verifyEntrypoints({ + config: appConfig, + paths: { + base: '/the/base/path', + }, + }) + ).toThrow( + `Entrypoint plugin.js must be located within the ./src directory` + ) + }) + + it(`does not throw an error if a plugin's entrypoint exists`, () => { + const appConfig = { + type: 'app', + entryPoints: { + plugin: './src/plugin.js', + }, + } + const resolveModule = jest.fn() + + expect(() => + verifyEntrypoints({ + config: appConfig, + paths: { + base: '/the/base/path', + }, + resolveModule, + }) + ).not.toThrow() + expect(resolveModule).toHaveBeenCalledWith( + '/the/base/path/src/plugin.js' + ) + }) + + it(`throws an error if a plugin's entrypoint does not exist`, () => { + const appConfig = { + type: 'app', + entryPoints: { + plugin: './src/plugin.js', + }, + } + const resolveModule = jest.fn(path => { + throw new Error(`Cannot find module '${path}'`) + }) + + const expectedError = 'Could not resolve entrypoint ./src/plugin.js' + expect(() => + verifyEntrypoints({ + config: appConfig, + paths: { + base: '/the/base/path', + }, + resolveModule, + }) + ).toThrow(expectedError) + expect(reporter.error).toHaveBeenCalledWith(expectedError) + expect(resolveModule).toHaveBeenCalledWith( + '/the/base/path/src/plugin.js' + ) + }) + + it(`throws an error if an app does not define any entrypoints`, () => { + const resolveModule = jest.fn() + const paths = { + base: '/the/base/path', + } + + const appConfig1 = { + type: 'app', + entryPoints: {}, + } + const appConfig2 = { + type: 'app', + entryPoints: undefined, + } + + const expectedAppError = 'Apps must define an app or plugin entrypoint' + expect(() => + verifyEntrypoints({ + config: appConfig1, + paths, + resolveModule, + }) + ).toThrow(expectedAppError) + expect(() => + verifyEntrypoints({ + config: appConfig2, + paths, + resolveModule, + }) + ).toThrow(expectedAppError) + + const libConfig1 = { + type: 'lib', + entryPoints: {}, + } + const libConfig2 = { + type: 'lib', + entryPoints: undefined, + } + + const expectedLibError = 'Libraries must define a lib entrypoint' + expect(() => + verifyEntrypoints({ + config: libConfig1, + paths, + resolveModule, + }) + ).toThrow(expectedLibError) + expect(() => + verifyEntrypoints({ + config: libConfig2, + paths, + resolveModule, + }) + ).toThrow(expectedLibError) + }) }) From 8e4dbfffdc923d9810d79f2faa739658f8ab768a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9di-R=C3=A9mi=20Hashim?= Date: Mon, 21 Feb 2022 13:14:22 +0000 Subject: [PATCH 02/35] feat(cli): create plugin entrypoint wrapper during compilation --- cli/src/lib/compiler/compile.js | 11 +++++++++-- cli/src/lib/compiler/entrypoints.js | 20 ++++++++++++++++---- cli/src/lib/paths.js | 1 + cli/src/lib/shell/index.js | 1 + shell/public/index.html | 2 +- shell/public/plugin.html | 26 ++++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 shell/public/plugin.html diff --git a/cli/src/lib/compiler/compile.js b/cli/src/lib/compiler/compile.js index c56036897..775ef8836 100644 --- a/cli/src/lib/compiler/compile.js +++ b/cli/src/lib/compiler/compile.js @@ -6,7 +6,8 @@ const fs = require('fs-extra') const makeBabelConfig = require('../../../config/makeBabelConfig.js') const { verifyEntrypoints, - overwriteAppEntrypoint, + createAppEntrypointWrapper, + createPluginEntrypointWrapper, } = require('./entrypoints.js') const { extensionPattern, @@ -70,10 +71,16 @@ const compile = async ({ verifyEntrypoints({ config, paths }) if (isApp) { - await overwriteAppEntrypoint({ + await createAppEntrypointWrapper({ entrypoint: config.entryPoints.app, paths, }) + if (config.entryPoints.plugin) { + await createPluginEntrypointWrapper({ + entrypoint: config.entryPoints.plugin, + paths, + }) + } } const outDir = isApp diff --git a/cli/src/lib/compiler/entrypoints.js b/cli/src/lib/compiler/entrypoints.js index 6b245ca4d..11981b74f 100644 --- a/cli/src/lib/compiler/entrypoints.js +++ b/cli/src/lib/compiler/entrypoints.js @@ -79,14 +79,26 @@ exports.verifyEntrypoints = ({ verifyLibraryEntrypoint(config.entryPoints.lib) } -exports.overwriteAppEntrypoint = async ({ entrypoint, paths }) => { +const getEntrypointWrapper = async ({ entrypoint, paths }) => { const relativeEntrypoint = entrypoint.replace(/^(\.\/)?src\//, '') const outRelativeEntrypoint = normalizeExtension(relativeEntrypoint) const shellAppSource = await fs.readFile(paths.shellSourceEntrypoint) + + return shellAppSource + .toString() + .replace(/'.\/D2App\/app'/g, `'./D2App/${outRelativeEntrypoint}'`) +} + +exports.createAppEntrypointWrapper = async ({ entrypoint, paths }) => { await fs.writeFile( paths.shellAppEntrypoint, - shellAppSource - .toString() - .replace(/'.\/D2App\/app'/g, `'./D2App/${outRelativeEntrypoint}'`) + await getEntrypointWrapper({ entrypoint, paths }) + ) +} + +exports.createPluginEntrypointWrapper = async ({ entrypoint, paths }) => { + await fs.writeFile( + paths.shellPluginEntrypoint, + await getEntrypointWrapper({ entrypoint, paths }) ) } diff --git a/cli/src/lib/paths.js b/cli/src/lib/paths.js index 93111cabe..1263c37bd 100644 --- a/cli/src/lib/paths.js +++ b/cli/src/lib/paths.js @@ -60,6 +60,7 @@ module.exports = (cwd = process.cwd()) => { shellAppEntrypoint: path.join(base, './.d2/shell/src/App.js'), shellAppDirname, shellApp: path.join(base, `./.d2/shell/${shellAppDirname}`), + shellPluginEntrypoint: path.join(base, './.d2/shell/src/Plugin.js'), shellSrcServiceWorker: path.join( base, './.d2/shell/src/service-worker.js' diff --git a/cli/src/lib/shell/index.js b/cli/src/lib/shell/index.js index e25105474..efacbffef 100644 --- a/cli/src/lib/shell/index.js +++ b/cli/src/lib/shell/index.js @@ -29,6 +29,7 @@ module.exports = ({ config, paths }) => ({ pipe: false, }) }, + // TODO: remove? Test command does not seem to call this method test: async () => { await exec({ cmd: 'yarn', diff --git a/shell/public/index.html b/shell/public/index.html index b0aa52aad..a637da98c 100644 --- a/shell/public/index.html +++ b/shell/public/index.html @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - diff --git a/shell/public/plugin.html b/shell/public/plugin.html new file mode 100644 index 000000000..97986f350 --- /dev/null +++ b/shell/public/plugin.html @@ -0,0 +1,26 @@ + + + + + + %REACT_APP_DHIS2_APP_NAME% plugin | DHIS2 + + + +
+
+ + + From 3e4275c9cf02b8d198ff075560f09b40ce865cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9di-R=C3=A9mi=20Hashim?= Date: Fri, 25 Feb 2022 15:44:25 +0000 Subject: [PATCH 03/35] feat: webpack config for plugin --- cli/src/commands/build.js | 6 + cli/src/commands/start.js | 19 +- cli/src/lib/paths.js | 9 + cli/src/lib/plugin/build.js | 38 ++ cli/src/lib/plugin/index.js | 17 + cli/src/lib/plugin/start.js | 3 + cli/src/lib/plugin/webpack.config.js | 573 +++++++++++++++++++++++++++ shell/public/plugin.html | 2 +- shell/src/Plugin.js | 1 + shell/src/plugin.js | 14 + 10 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 cli/src/lib/plugin/build.js create mode 100644 cli/src/lib/plugin/index.js create mode 100644 cli/src/lib/plugin/start.js create mode 100644 cli/src/lib/plugin/webpack.config.js create mode 100644 shell/src/Plugin.js create mode 100644 shell/src/plugin.js diff --git a/cli/src/commands/build.js b/cli/src/commands/build.js index b6d81decf..6049159da 100644 --- a/cli/src/commands/build.js +++ b/cli/src/commands/build.js @@ -8,6 +8,7 @@ const i18n = require('../lib/i18n') const loadEnvFiles = require('../lib/loadEnvFiles') const parseConfig = require('../lib/parseConfig') const makePaths = require('../lib/paths') +const makePlugin = require('../lib/plugin') const { injectPrecacheManifest } = require('../lib/pwa') const makeShell = require('../lib/shell') const { validatePackage } = require('../lib/validatePackage') @@ -67,6 +68,7 @@ const handler = async ({ const config = parseConfig(paths) const shell = makeShell({ config, paths }) + const plugin = makePlugin({ config, paths }) if (config.type === 'app') { setAppParameters(standalone, config) @@ -129,6 +131,10 @@ const handler = async ({ reporter.info('Building appShell...') await shell.build() + if (config.entryPoints.plugin) { + await plugin.build() + } + if (config.pwa.enabled) { reporter.info('Injecting precache manifest...') await injectPrecacheManifest(paths, config) diff --git a/cli/src/commands/start.js b/cli/src/commands/start.js index 393489ab7..352e9e2b9 100644 --- a/cli/src/commands/start.js +++ b/cli/src/commands/start.js @@ -7,6 +7,7 @@ const i18n = require('../lib/i18n') const loadEnvFiles = require('../lib/loadEnvFiles') const parseConfig = require('../lib/parseConfig') const makePaths = require('../lib/paths') +const makePlugin = require('../lib/plugin') const createProxyServer = require('../lib/proxy') const { compileServiceWorker } = require('../lib/pwa') const makeShell = require('../lib/shell') @@ -29,6 +30,7 @@ const handler = async ({ const config = parseConfig(paths) const shell = makeShell({ config, paths }) + const plugin = makePlugin({ config, paths }) if (config.type !== 'app') { reporter.error( @@ -128,7 +130,22 @@ const handler = async ({ ) reporter.print('') - await shell.start({ port: newPort }) + const shellStartPromise = shell.start({ port: newPort }) + + if (config.entryPoints.plugin) { + const pluginPort = await detectPort(newPort) + reporter.print( + `The plugin is now available on port ${pluginPort}` + ) + reporter.print('') + + await Promise.all([ + shellStartPromise, + plugin.start({ port: pluginPort }), + ]) + } else { + await shellStartPromise + } }, { name: 'start', diff --git a/cli/src/lib/paths.js b/cli/src/lib/paths.js index 1263c37bd..305dafcea 100644 --- a/cli/src/lib/paths.js +++ b/cli/src/lib/paths.js @@ -57,15 +57,24 @@ module.exports = (cwd = process.cwd()) => { d2: path.join(base, './.d2/'), appOutputFilename: 'App.js', shell: path.join(base, './.d2/shell'), + shellSrc: path.join(base, './.d2/shell/src'), shellAppEntrypoint: path.join(base, './.d2/shell/src/App.js'), shellAppDirname, shellApp: path.join(base, `./.d2/shell/${shellAppDirname}`), + shellPluginBundleEntrypoint: path.join( + base, + './.d2/shell/src/plugin.js' + ), shellPluginEntrypoint: path.join(base, './.d2/shell/src/Plugin.js'), shellSrcServiceWorker: path.join( base, './.d2/shell/src/service-worker.js' ), shellPublic: path.join(base, './.d2/shell/public'), + shellPublicPluginHtml: path.join( + base, + './.d2/shell/public/plugin.html' + ), shellPublicServiceWorker: path.join( base, './.d2/shell/public/service-worker.js' diff --git a/cli/src/lib/plugin/build.js b/cli/src/lib/plugin/build.js new file mode 100644 index 000000000..74c1f0897 --- /dev/null +++ b/cli/src/lib/plugin/build.js @@ -0,0 +1,38 @@ +// Based on CRA build script + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'production' +process.env.NODE_ENV = 'production' + +const webpack = require('webpack') +const webpackConfigFactory = require('./webpack.config') + +module.exports = async ({ paths }) => { + console.log('Building plugin...') + + const webpackConfig = webpackConfigFactory({ env: 'production', paths }) + const compiler = webpack(webpackConfig) + return new Promise((resolve, reject) => { + compiler.run(err => { + if (err) { + if (!err.message) { + reject(err) + return + } + + let errMessage = err.message + // Add additional information for postcss errors + if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { + errMessage += + '\nCompileError: Begins at CSS selector ' + + err['postcssNode'].selector + } + + reject(new Error(errMessage)) + return + } + + resolve() + }) + }) +} diff --git a/cli/src/lib/plugin/index.js b/cli/src/lib/plugin/index.js new file mode 100644 index 000000000..f10f338b2 --- /dev/null +++ b/cli/src/lib/plugin/index.js @@ -0,0 +1,17 @@ +const build = require('./build') +const start = require('./start') + +module.exports = ({ config, paths }) => ({ + // build: () => build({ config, paths }), + build: async () => { + try { + await build({ config, paths }) + } catch (error) { + console.log('\n\nerror building plugin:') + console.error(error) + console.log('\n') + throw error + } + }, + start: ({ port }) => start({ port, config, paths }), +}) diff --git a/cli/src/lib/plugin/start.js b/cli/src/lib/plugin/start.js new file mode 100644 index 000000000..48ea8debf --- /dev/null +++ b/cli/src/lib/plugin/start.js @@ -0,0 +1,3 @@ +// TODO +// module.exports = ({ port, config, paths }) => {} +module.exports = () => {} diff --git a/cli/src/lib/plugin/webpack.config.js b/cli/src/lib/plugin/webpack.config.js new file mode 100644 index 000000000..985bfc3eb --- /dev/null +++ b/cli/src/lib/plugin/webpack.config.js @@ -0,0 +1,573 @@ +// Based on CRA Webpack config + +const path = require('path') +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin') +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') +const postcssNormalize = require('postcss-normalize') +const safePostCssParser = require('postcss-safe-parser') +const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier') +const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent') +const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath') +const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin') +const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin') +const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin') +const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin') +const TerserPlugin = require('terser-webpack-plugin') +const webpack = require('webpack') +const ManifestPlugin = require('webpack-manifest-plugin') + +const webpackDevClientEntry = require.resolve( + 'react-dev-utils/webpackHotDevClient' +) +const reactRefreshOverlayEntry = require.resolve( + 'react-dev-utils/refreshOverlayInterop' +) + +const imageInlineSizeLimit = 10e3 + +// style files regexes +const cssRegex = /\.css$/ +const cssModuleRegex = /\.module\.css$/ +const sassRegex = /\.(scss|sass)$/ +const sassModuleRegex = /\.module\.(scss|sass)$/ + +module.exports = ({ env: webpackEnv, paths }) => { + const isProduction = webpackEnv === 'production' + const isDevelopment = !isProduction + const publicPath = getPublicUrlOrPath( + isDevelopment, + null, + process.env.PUBLIC_URL + ) + + // common function to get style loaders + const getStyleLoaders = (cssOptions, preProcessor) => { + const loaders = [ + isDevelopment && require.resolve('style-loader'), + isProduction && { + loader: MiniCssExtractPlugin.loader, + // css is located in `static/css`, use '../../' to locate plugin.html folder + // in production `publicPath` can be a relative path + options: publicPath.startsWith('.') + ? { publicPath: '../../' } + : {}, + }, + { + loader: require.resolve('css-loader'), + options: cssOptions, + }, + { + // Options for PostCSS as we reference these options twice + // Adds vendor prefixing based on your specified browser support in + // package.json + loader: require.resolve('postcss-loader'), + options: { + // Necessary for external CSS imports to work + // https://github.com/facebook/create-react-app/issues/2677 + ident: 'postcss', + plugins: () => [ + require('postcss-flexbugs-fixes'), + require('postcss-preset-env')({ + autoprefixer: { + flexbox: 'no-2009', + }, + stage: 3, + }), + // Adds PostCSS Normalize as the reset css with default options, + // so that it honors browserslist config in package.json + // which in turn let's users customize the target behavior as per their needs. + postcssNormalize(), + ], + sourceMap: true, + }, + }, + ].filter(Boolean) + if (preProcessor) { + loaders.push( + { + loader: require.resolve('resolve-url-loader'), + options: { + sourceMap: true, + root: paths.appSrc, + }, + }, + { + loader: require.resolve(preProcessor), + options: { + sourceMap: true, + }, + } + ) + } + return loaders + } + + return { + mode: webpackEnv, + bail: isProduction, + entry: isDevelopment + ? [webpackDevClientEntry, paths.shellPluginBundleEntrypoint] + : paths.shellPluginBundleEntrypoint, + output: { + path: isProduction ? paths.shellBuildOutput : undefined, + pathinfo: isDevelopment, + filename: isProduction + ? 'static/js/[name].[contenthash:8].js' + : 'static/js/bundle.js', + chunkFilename: isProduction + ? 'static/js/[name].[contenthash:8].chunk.js' + : 'static/js/[name].chunk.js', + publicPath, + devtoolModuleFilenameTemplate: isProduction + ? info => + path + .relative(paths.shellSrc, info.absoluteResourcePath) + .replace(/\\/g, '/') + : info => + path + .resolve(info.absoluteResourcePath) + .replace(/\\/g, '/'), + globalObject: 'this', + }, + optimization: { + minimize: isProduction, + minimizer: [ + // This is only used in production mode + new TerserPlugin({ + terserOptions: { + parse: { + // We want terser to parse ecma 8 code. However, we don't want it + // to apply any minification steps that turns valid ecma 5 code + // into invalid ecma 5 code. This is why the 'compress' and 'output' + // sections only apply transformations that are ecma 5 safe + // https://github.com/facebook/create-react-app/pull/4234 + ecma: 8, + }, + compress: { + ecma: 5, + warnings: false, + // Disabled because of an issue with Uglify breaking seemingly valid code: + // https://github.com/facebook/create-react-app/issues/2376 + // Pending further investigation: + // https://github.com/mishoo/UglifyJS2/issues/2011 + comparisons: false, + // Disabled because of an issue with Terser breaking valid code: + // https://github.com/facebook/create-react-app/issues/5250 + // Pending further investigation: + // https://github.com/terser-js/terser/issues/120 + inline: 2, + }, + mangle: { + safari10: true, + }, + output: { + ecma: 5, + comments: false, + // Turned on because emoji and regex is not minified properly using default + // https://github.com/facebook/create-react-app/issues/2488 + ascii_only: true, + }, + }, + }), + // This is only used in production mode + new OptimizeCSSAssetsPlugin({ + cssProcessorOptions: { + parser: safePostCssParser, + map: { + // `inline: false` forces the sourcemap to be output into a + // separate file + inline: false, + // `annotation: true` appends the sourceMappingURL to the end of + // the css file, helping the browser find the sourcemap + annotation: true, + }, + }, + cssProcessorPluginOptions: { + preset: [ + 'default', + { minifyFontValues: { removeQuotes: false } }, + ], + }, + }), + ], + // Automatically split vendor and commons + // https://twitter.com/wSokra/status/969633336732905474 + // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 + splitChunks: { + chunks: 'all', + name: isDevelopment, + }, + // Keep the runtime chunk separated to enable long term caching + // https://twitter.com/wSokra/status/969679223278505985 + // https://github.com/facebook/create-react-app/issues/5358 + runtimeChunk: { + name: entrypoint => `runtime-${entrypoint.name}`, + }, + }, + resolve: { + extensions: ['js', 'ts', 'tsx', 'jsx', 'json', 'mjs'], + }, + module: { + strictExportPresence: true, + rules: [ + // Disable require.ensure as it's not a standard language feature. + { parser: { requireEnsure: false } }, + { + // "oneOf" will traverse all following loaders until one will + // match the requirements. When no loader matches it will fall + // back to the "file" loader at the end of the loader list. + oneOf: [ + // TODO: Merge this config once `image/avif` is in the mime-db + // https://github.com/jshttp/mime-db + { + test: [/\.avif$/], + loader: require.resolve('url-loader'), + options: { + limit: imageInlineSizeLimit, + mimetype: 'image/avif', + name: 'static/media/[name].[hash:8].[ext]', + }, + }, + // "url" loader works like "file" loader except that it embeds assets + // smaller than specified limit in bytes as data URLs to avoid requests. + // A missing `test` is equivalent to a match. + { + test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], + loader: require.resolve('url-loader'), + options: { + limit: imageInlineSizeLimit, + name: 'static/media/[name].[hash:8].[ext]', + }, + }, + + // TODO: remove babel-loader code + + // Process application JS with Babel. + // The preset includes JSX, Flow, TypeScript, and some ESnext features. + /* + { + test: /\.(js|mjs|jsx|ts|tsx)$/, + include: paths.shellSrc, + loader: require.resolve('babel-loader'), + options: { + customize: require.resolve( + 'babel-preset-react-app/webpack-overrides' + ), + presets: [ + [ + require.resolve( + 'babel-preset-react-app' + ), + { + runtime: 'classic', + }, + ], + ], + babelrc: false, + configFile: false, + // Make sure we have a unique cache identifier, erring on the + // side of caution. + cacheIdentifier: getCacheIdentifier( + webpackEnv, + [ + 'babel-plugin-named-asset-import', + 'babel-preset-react-app', + 'react-dev-utils', + 'react-scripts', + ] + ), + plugins: [ + [ + require.resolve( + 'babel-plugin-named-asset-import' + ), + { + loaderMap: { + svg: { + ReactComponent: + '@svgr/webpack?-svgo,+titleProp,+ref![path]', + }, + }, + }, + ], + isDevelopment && + require.resolve('react-refresh/babel'), + ].filter(Boolean), + // This is a feature of `babel-loader` for webpack (not Babel itself). + // It enables caching results in ./node_modules/.cache/babel-loader/ + // directory for faster rebuilds. + cacheDirectory: true, + // See #6846 for context on why cacheCompression is disabled + cacheCompression: false, + compact: isProduction, + }, + }, + // Process any JS outside of the app with Babel. + // Unlike the application JS, we only compile the standard ES features. + { + test: /\.(js|mjs)$/, + exclude: /@babel(?:\/|\\{1,2})runtime/, + loader: require.resolve('babel-loader'), + options: { + babelrc: false, + configFile: false, + compact: false, + presets: [ + [ + require.resolve( + 'babel-preset-react-app/dependencies' + ), + { helpers: true }, + ], + ], + cacheDirectory: true, + // See #6846 for context on why cacheCompression is disabled + cacheCompression: false, + cacheIdentifier: getCacheIdentifier( + webpackEnv, + [ + 'babel-plugin-named-asset-import', + 'babel-preset-react-app', + 'react-dev-utils', + 'react-scripts', + ] + ), + // Babel sourcemaps are needed for debugging into node_modules + // code. Without the options below, debuggers like VSCode + // show incorrect code and set breakpoints on the wrong lines. + sourceMaps: true, + inputSourceMap: true, + }, + }, + */ + // "postcss" loader applies autoprefixer to our CSS. + // "css" loader resolves paths in CSS and adds assets as dependencies. + // "style" loader turns CSS into JS modules that inject - + {!plugin && }
window.location.reload()}> {children} @@ -36,4 +36,5 @@ AppWrapper.propTypes = { appName: PropTypes.string.isRequired, offlineInterface: PropTypes.object.isRequired, children: PropTypes.node, + plugin: PropTypes.bool, } diff --git a/adapter/src/index.js b/adapter/src/index.js index 9492ec9d9..c6f922c96 100644 --- a/adapter/src/index.js +++ b/adapter/src/index.js @@ -7,7 +7,14 @@ import { ServerVersionProvider } from './components/ServerVersionProvider.js' const offlineInterface = new OfflineInterface() -const AppAdapter = ({ url, apiVersion, appName, pwaEnabled, children }) => ( +const AppAdapter = ({ + url, + apiVersion, + appName, + pwaEnabled, + plugin, + children, +}) => ( ( pwaEnabled={pwaEnabled} offlineInterface={offlineInterface} > - + {children} @@ -26,6 +37,7 @@ AppAdapter.propTypes = { appName: PropTypes.string.isRequired, apiVersion: PropTypes.number, children: PropTypes.element, + plugin: PropTypes.bool, pwaEnabled: PropTypes.bool, url: PropTypes.string, } diff --git a/cli/src/lib/plugin/webpack.config.js b/cli/src/lib/plugin/webpack.config.js index 8a8307c38..9298cfe9f 100644 --- a/cli/src/lib/plugin/webpack.config.js +++ b/cli/src/lib/plugin/webpack.config.js @@ -31,7 +31,7 @@ module.exports = ({ env: webpackEnv, paths }) => { process.env.PUBLIC_URL ) - const shellEnv = getShellEnv({}) + const shellEnv = getShellEnv({ plugin: 'true' }) // "style" loader turns CSS into JS modules that inject