Skip to content

Commit

Permalink
Upgrade to webpack 5 for website, update other dependencies (#706)
Browse files Browse the repository at this point in the history
The main challenges with swtiching to webpack 5 were:
* Webpack 5 dropped automatic implementations of node packages and globals, so I
  needed to add them back in order for babel and TS to work properly.
* Webpack 5 implemented native support for workers as their own bundle.
  Unfortunately, this still has the issue where the worker is blocked while the
  import is happening, so I kept the existing implementation of using `fetch` to
  pre-fetch the dependency so that the worker is interactive in the meantime.
  Webpack changed its chunk file naming, so I needed to update the logic to
  account for that.

This PR also removed a few other things from create-react-app that we weren't
using.
  • Loading branch information
alangpierce authored Jun 22, 2022
1 parent 3c16843 commit c6cfacc
Show file tree
Hide file tree
Showing 8 changed files with 495 additions and 1,311 deletions.
40 changes: 15 additions & 25 deletions website/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WorkerPlugin = require("worker-plugin");
const getClientEnvironment = require("./env");
const paths = require("./paths");

Expand All @@ -32,12 +31,6 @@ module.exports = {
output: {
// Next line is not used in dev but WebpackDevServer crashes without it:
path: paths.appBuild,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: true,
// This does not produce a real file. It's just the virtual path that is
// served by WebpackDevServer in development. This is the JS bundle
// containing code from all our entry points, and the Webpack runtime.
filename: "bundle.js",
// There are also additional JS chunk files if you use code splitting.
chunkFilename: "[name].chunk.js",
// This is the URL that app is served from. We use "/" in development.
Expand All @@ -54,12 +47,14 @@ module.exports = {
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ["node_modules", paths.appNodeModules],
extensions: [".js", ".mjs", ".json", ".ts", ".tsx"],
alias: {
// TypeScript requires this node-internal module in code we don't use.
perf_hooks: false,
},
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{parser: {requireEnsure: false}},
{
type: "javascript/auto",
test: /\.mjs$/,
Expand Down Expand Up @@ -135,28 +130,23 @@ module.exports = {
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebookincubator/create-react-app/issues/240
new CaseSensitivePathsPlugin(),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new MonacoWebpackPlugin({languages: ["typescript"]}),
new WorkerPlugin(),
// babel-plugin-jest-hoist pulls in @babel/template, which transitively
// pulls in chalk, which has a dependency on process.
new webpack.ProvidePlugin({process: "process/browser"}),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty",
},
// Turn off performance hints during development because we don't do any
// splitting or minification in interest of speed. These warnings become
// cumbersome.
performance: {
hints: false,
},
optimization: {
splitChunks: {
// Don't split async chunks. This is needed for the prefetchChunk function
// to work right; Babel and TS should be loaded as one large chunk each so
// that we can fully pre-fetch them.
chunks: "initial",
},
},
};
34 changes: 16 additions & 18 deletions website/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WorkerPlugin = require("worker-plugin");
const paths = require("./paths");
const getClientEnvironment = require("./env");

Expand Down Expand Up @@ -45,8 +43,8 @@ module.exports = {
// Generated JS file names (with nested folders).
// There will be one main bundle, and one file per asynchronous chunk.
// We don't currently advertise code splitting but Webpack supports it.
filename: "[name].[hash:8].js",
chunkFilename: "[name].[hash:8].chunk.js",
filename: "[name].[fullhash:8].js",
chunkFilename: "[name].[fullhash:8].chunk.js",
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
Expand All @@ -61,12 +59,14 @@ module.exports = {
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ["node_modules", paths.appNodeModules],
extensions: [".js", ".mjs", ".json", ".ts", ".tsx"],
alias: {
// TypeScript requires this node-internal module in code we don't use.
perf_hooks: false,
},
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{parser: {requireEnsure: false}},
{
type: "javascript/auto",
test: /\.mjs$/,
Expand Down Expand Up @@ -152,19 +152,17 @@ module.exports = {
// It is absolutely essential that NODE_ENV was set to production here.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
new ManifestPlugin({
fileName: "asset-manifest.json",
}),
new MonacoWebpackPlugin({languages: ["typescript"]}),
new WorkerPlugin(),
// babel-plugin-jest-hoist pulls in @babel/template, which transitively
// pulls in chalk, which has a dependency on process.
new webpack.ProvidePlugin({process: "process/browser"}),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty",
optimization: {
splitChunks: {
// Don't split async chunks. This is needed for the prefetchChunk function
// to work right; Babel and TS should be loaded as one large chunk each so
// that we can fully pre-fetch them.
chunks: "initial",
},
},
};
23 changes: 10 additions & 13 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
"@types/gzip-js": "^0.3.1",
"@types/react": "^16",
"@types/react-dom": "^16.9.4",
"@types/react-virtualized": "^9.21.5",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"aphrodite": "^2.4.0",
"babel-core": "^6.26.3",
"babel-jest": "^24.9.0",
"babel-plugin-dynamic-import-node": "^2.3.0",
"babel-plugin-jest-hoist": "^26.6.2",
"babel-plugin-jest-hoist": "^28.1.1",
"babel-runtime": "^6.26.0",
"base64-js": "^1.3.1",
"case-sensitive-paths-webpack-plugin": "^2.2.0",
Expand All @@ -27,24 +27,21 @@
"gzip-js": "^0.3.2",
"html-webpack-plugin": "^4.0.0-beta.5",
"jest": "^26.6.3",
"monaco-editor": "^0.19.0",
"monaco-editor-webpack-plugin": "^1.7.0",
"object-assign": "^4.1.1",
"promise": "^8.0.3",
"monaco-editor": "^0.33.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"process": "^0.11.10",
"react": "^16.11.0",
"react-dev-utils": "^12.0.0-next.47",
"react-dom": "^16.11.0",
"react-hot-loader": "^4.12.16",
"react-monaco-editor": "^0.32.1",
"react-virtualized": "^9.21.1",
"react-monaco-editor": "^0.48.0",
"react-virtualized-auto-sizer": "^1.0.6",
"style-loader": "^1.0.0",
"sucrase": "^3.21.1",
"typescript": "^4.1.3",
"typescript": "^4.7.4",
"url-loader": "^2.2.0",
"webpack": "^4.41.2",
"webpack-dev-server": "^4.9.2",
"webpack-manifest-plugin": "^2.2.0",
"worker-plugin": "^3.2.0"
"webpack": "^5.73.0",
"webpack-dev-server": "^4.9.2"
},
"scripts": {
"start": "node scripts/start.js",
Expand Down
2 changes: 1 addition & 1 deletion website/src/EditorWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {css, StyleSheet} from "aphrodite";
import {editor} from "monaco-editor";
import React, {Component} from "react";
import {AutoSizer} from "react-virtualized/dist/es/AutoSizer";
import AutoSizer from "react-virtualized-auto-sizer";

import Editor from "./Editor";
import FallbackEditor from "./FallbackEditor";
Expand Down
17 changes: 12 additions & 5 deletions website/src/Worker.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ function postMessage(message: WorkerMessage): void {

/**
* Hacky workaround for the fact that chunk loading in workers is normally
* synchronous. We determine the chunk name based on how the webpack config
* chooses names, then load that up-front in a fetch (which is actually async).
* This makes it so the later import() will be able to pull the JS from cache,
* and thus block the worker for a much shorter period of time.
* synchronous (importScripts). We determine the chunk name based on how the
* webpack config chooses names, then load that up-front in a fetch (which is
* actually async). This makes it so the later import() will be able to pull
* the JS from cache, and thus block the worker for a much shorter period of
* time.
*
* TODO: Figure out how to get webpack to use true ESM imports for async loading
* in workers, which would avoid this need.
*/
async function prefetchChunk(chunkName: string): Promise<void> {
const path = location.pathname.replace(/\/\d+\./, `/${chunkName}.`);
// Chunks have the filename "[name].[fullhash:8].chunk.js", so we can chop off
// the first part of this chunk's name and replace it with the chunkName to
// get the filename of the chunk to load.
const path = location.pathname.replace(/\/[^.]+\./, `/${chunkName}.`);
const response = await fetch(path);
await response.text();
}
Expand Down
2 changes: 1 addition & 1 deletion website/src/WorkerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type HandleCompressedCodeFunc = (compressedCode: string) => void;
let handleCompressedCodeFn: HandleCompressedCodeFunc | null = null;

function initWorker(): void {
worker = new Worker("./Worker.worker", {type: "module"});
worker = new Worker(new URL("./Worker.worker", import.meta.url));
worker.addEventListener("message", ({data}: {data: WorkerMessage}) => {
if (data.type === "RESPONSE") {
if (!nextResolve) {
Expand Down
7 changes: 4 additions & 3 deletions website/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"compilerOptions": {
"strictNullChecks": true,
"target": "es2017",
"module": "commonjs",
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"typeRoots": ["node_modules/@types"],
"esModuleInterop": true,
"isolatedModules": true,
Expand All @@ -12,7 +13,7 @@
"suppressImplicitAnyIndexErrors": true,
"downlevelIteration": true,
"noEmitHelpers": true,
"importHelpers": true,
"importHelpers": true
},
"exclude": [
"build",
Expand Down
Loading

0 comments on commit c6cfacc

Please sign in to comment.