Support react-refresh and shared client/server types (#98)
- Adds support for react-refresh through `@pmmwh/react-refresh-webpack-plugin`. Updated webpack config to support this. Means entire app is no longer refreshed on changes, only individual components, and state is maintained. - React refresh removes need for `google-apps-script-webpack-dev-server` package - In client code, support for server function autocomplete through use of gas-client 1.0.0. Requires certain files to be typescript .ts files - Remove need for "global" exports in main server file through updated gas-webpack-plugin
This commit is contained in:
+42
-19
@@ -11,6 +11,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||
const DynamicCdnWebpackPlugin = require('dynamic-cdn-webpack-plugin');
|
||||
const moduleToCdn = require('module-to-cdn');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
|
||||
/*********************************
|
||||
* set up environment variables
|
||||
@@ -32,7 +33,7 @@ const isProd = process.env.NODE_ENV === 'production';
|
||||
const destination = path.resolve(__dirname, 'dist');
|
||||
|
||||
// define server paths
|
||||
const serverEntry = './src/server/index.js';
|
||||
const serverEntry = './src/server/index.ts';
|
||||
|
||||
// define appsscript.json file path
|
||||
const copyAppscriptEntry = './appsscript.json';
|
||||
@@ -98,7 +99,7 @@ const sharedClientAndServerConfig = {
|
||||
};
|
||||
|
||||
// webpack settings used by all client entrypoints
|
||||
const clientConfig = {
|
||||
const clientConfig = ({ isDevClientWrapper }) => ({
|
||||
...sharedClientAndServerConfig,
|
||||
mode: isProd ? 'production' : 'development',
|
||||
output: {
|
||||
@@ -119,6 +120,14 @@ const clientConfig = {
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
// only enable react-refresh for dev builds, and not when building the dev client "wrapper"
|
||||
options: {
|
||||
plugins: [
|
||||
!isProd &&
|
||||
!isDevClientWrapper &&
|
||||
require.resolve('react-refresh/babel'),
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
@@ -130,6 +139,14 @@ const clientConfig = {
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
// only enable react-refresh for dev builds, and not when building the dev client "wrapper"
|
||||
options: {
|
||||
plugins: [
|
||||
!isProd &&
|
||||
!isDevClientWrapper &&
|
||||
require.resolve('react-refresh/babel'),
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
},
|
||||
// we could add support for scss here
|
||||
@@ -139,7 +156,7 @@ const clientConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// DynamicCdnWebpackPlugin settings
|
||||
// these settings help us load 'react', 'react-dom' and the packages defined below from a CDN
|
||||
@@ -150,9 +167,19 @@ const DynamicCdnWebpackPluginConfig = {
|
||||
resolver: (packageName, packageVersion, options) => {
|
||||
const packageSuffix = isProd ? '.min.js' : '.js';
|
||||
const moduleDetails = moduleToCdn(packageName, packageVersion, options);
|
||||
|
||||
// don't externalize react during development due to issue with react-refresh
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/334
|
||||
if (!isProd && packageName === 'react') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// return defaults if Dynamic CDN plugin finds package
|
||||
if (moduleDetails) {
|
||||
return moduleDetails;
|
||||
}
|
||||
|
||||
// define custom CDN configuration for new packages
|
||||
// "name" should match the package being imported
|
||||
// "var" is important to get right -- this should be the exposed global. Look up "webpack externals" for info.
|
||||
switch (packageName) {
|
||||
@@ -178,11 +205,14 @@ const DynamicCdnWebpackPluginConfig = {
|
||||
|
||||
// webpack settings used by each client entrypoint defined at top
|
||||
const clientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
||||
const isDevClientWrapper = false;
|
||||
return {
|
||||
...clientConfig,
|
||||
...clientConfig({ isDevClientWrapper }),
|
||||
name: clientEntrypoint.name,
|
||||
entry: clientEntrypoint.entry,
|
||||
plugins: [
|
||||
!isProd && new webpack.HotModuleReplacementPlugin(),
|
||||
!isProd && new ReactRefreshWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify(envVars),
|
||||
}),
|
||||
@@ -195,26 +225,15 @@ const clientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
||||
new HtmlWebpackInlineSourcePlugin(),
|
||||
// this plugin allows us to add dynamically load packages from a CDN
|
||||
new DynamicCdnWebpackPlugin(DynamicCdnWebpackPluginConfig),
|
||||
],
|
||||
].filter(Boolean),
|
||||
};
|
||||
});
|
||||
|
||||
const gasWebpackDevServerPath = require.resolve(
|
||||
'google-apps-script-webpack-dev-server'
|
||||
);
|
||||
|
||||
// webpack settings for devServer https://webpack.js.org/configuration/dev-server/
|
||||
const devServer = {
|
||||
hot: true,
|
||||
port: PORT,
|
||||
https: true,
|
||||
// run our own route to serve the package google-apps-script-webpack-dev-server
|
||||
before: app => {
|
||||
// this '/gas/' path needs to match the path loaded in the iframe in dev/index.js
|
||||
app.get('/gas/*', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
fs.createReadStream(gasWebpackDevServerPath).pipe(res);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||
@@ -238,8 +257,9 @@ if (fs.existsSync(pfxPath)) {
|
||||
// webpack settings for the development client wrapper
|
||||
const devClientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
||||
envVars.FILENAME = clientEntrypoint.filename;
|
||||
const isDevClientWrapper = true;
|
||||
return {
|
||||
...clientConfig,
|
||||
...clientConfig({ isDevClientWrapper }),
|
||||
name: `DEVELOPMENT: ${clientEntrypoint.name}`,
|
||||
entry: devDialogEntry,
|
||||
plugins: [
|
||||
@@ -332,7 +352,10 @@ const serverConfig = {
|
||||
isProd ? 'production' : 'development'
|
||||
),
|
||||
}),
|
||||
new GasPlugin(),
|
||||
new GasPlugin({
|
||||
// removes need for assigning public server functions to "global"
|
||||
autoGlobalExportsFiles: [serverEntry],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user