(webpack) upgrade to webpack 5

refs FOO-2835
flag = none

there are no behavioral changes, only what was necessary to do the
upgrade, see inline comments for explanation where it was warranted

Change-Id: I737f31a210365f190ddb606e96e8eb84e2359e0d
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/294133
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Aaron Ogata <aogata@instructure.com>
Reviewed-by: Charley Kline <ckline@instructure.com>
QA-Review: Aaron Ogata <aogata@instructure.com>
Product-Review: Aaron Ogata <aogata@instructure.com>
This commit is contained in:
Ahmad Amireh 2022-06-17 08:54:48 +03:00
parent 8f3f9f19ed
commit 55713bb8e8
11 changed files with 589 additions and 279 deletions

View File

@ -19,7 +19,7 @@
const karmaConfig = {
basePath: '',
frameworks: ['qunit'],
frameworks: ['qunit', 'webpack'],
proxies: {
'/dist/brandable_css/': '/base/public/dist/brandable_css/',

View File

@ -42,7 +42,11 @@ module Canvas
end
def scripts_for(bundle)
@manifest.fetch(bundle, []).map { |s| realpath(s) }
if @manifest.key?(bundle)
[realpath(@manifest[bundle])]
else
[]
end
end
private

View File

@ -231,7 +231,6 @@
"babel-plugin-transform-react-remove-prop-types": "^0.4",
"babel-plugin-typescript-to-proptypes": "^1.4.2",
"chai-assert-change": "^2.0.0",
"clean-webpack-plugin": "^3",
"coffee-loader": "~0.7.2",
"coffee-script": "^1",
"concurrently": "^4",
@ -300,14 +299,14 @@
"karma-sourcemap-loader": "^0.3",
"karma-spec-reporter": "^0.0.32",
"karma-verbose-reporter": "^0.0.6",
"karma-webpack": "instructure/karma-webpack#patch1",
"karma-webpack": "^5",
"lint-staged": "^9",
"loader-utils": "^1",
"merge-stream": "^2",
"micromatch": "^4.0.4",
"mkdirp": "^1.0.4",
"mockdate": "^2.0.2",
"moment-timezone-data-webpack-plugin": "^1.0.3",
"moment-timezone-data-webpack-plugin": "^1.5.0",
"moxios": "^0.4",
"msw": "^0.27.2",
"nyc": "^13",
@ -320,20 +319,18 @@
"sinon": "^7",
"style-loader": "^0.23",
"stylelint": "^10",
"terser-webpack-plugin": "^1.4.3",
"terser-webpack-plugin": "5.3.3",
"through2": "^3",
"timezone-mock": "^1.3.1",
"tinymce": "^5",
"tsc-files": "^1.1.3",
"typescript": "^4.3.5",
"waait": "^1",
"webpack": "^4",
"webpack-bundle-analyzer": "^4.4.1",
"webpack-cleanup-plugin": "^0.5",
"webpack": "^5",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4",
"webpack-encapsulation-plugin": "^2.0.0",
"webpack-manifest-plugin": "^2",
"webpack-stats-plugin": "^0.2.1",
"webpack-manifest-plugin": "^5",
"wrap-ansi": "^7.0.0",
"wsrun": "^5",
"xsslint": "instructure/xsslint#babel7",

View File

@ -31,8 +31,8 @@
"debug": "inspect _mocha --no-timeouts --debug-brk 'test/**/*.test.js'",
"demo": "scripts/demo.sh",
"demo:clean": "rm -f github-pages/dist/*",
"demo:build": "webpack -c ./webpack.demo.config.js",
"demo:dev": "yarn demo:clean && mkdir -p ./github-pages/dist && cp ./github-pages/index.html ./github-pages/dist && webpack -c ./webpack.dev.config.js",
"demo:build": "wp -c ./webpack.demo.config.js",
"demo:dev": "yarn demo:clean && mkdir -p ./github-pages/dist && cp ./github-pages/index.html ./github-pages/dist && wp -c ./webpack.dev.config.js",
"installTranslations": "scripts/installTranslations.js",
"commitTranslations": "scripts/commitTranslations.sh",
"build:all": "scripts/build.js",
@ -40,7 +40,7 @@
"build:cjs": "babel --out-dir lib src --ignore '**/__tests__,**/__mocks__' --config-file=./babel.config.cjs.js",
"build:canvas": "scripts/build-canvas",
"build:watch": "rm -rf es && babel --out-dir es src --watch --verbose",
"build:cafe": "webpack --config webpack.testcafe.config.js",
"build:cafe": "wp --config webpack.testcafe.config.js",
"prepublishOnly": "yarn build:all && yarn test",
"fmt:check": "prettier -l '**/*.js'",
"fmt:fix": "prettier --write '**/*.js'",

View File

@ -16,7 +16,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { configure } from './'
import { configure } from 'datetime'
import timezone from 'timezone'
const snapshots = []

View File

@ -74,10 +74,9 @@ describe ::Canvas::Cdn::Registry do
end
it "is true given the path to a javascript produced by webpack" do
@webpack_manifest = { "main" => ["a-1234.js", "b-1234.js"] }
@webpack_manifest = { "main" => "a-1234.js" }
expect(subject.include?("/dist/webpack-dev/a-1234.js")).to eq(true)
expect(subject.include?("/dist/webpack-dev/b-1234.js")).to eq(true)
expect(subject.include?("a-1234.js")).to eq(false)
expect(subject.include?("main")).to eq(false)
end
@ -85,12 +84,11 @@ describe ::Canvas::Cdn::Registry do
describe ".scripts_for" do
it "returns realpaths to files within the bundle" do
@webpack_manifest = { "main" => ["a-1234.js", "b-1234.js"] }
@webpack_manifest = { "main" => "a-1234.js" }
expect(subject.scripts_for("main")).to eq(
[
"/dist/webpack-dev/a-1234.js",
"/dist/webpack-dev/b-1234.js",
"/dist/webpack-dev/a-1234.js"
]
)
end

View File

@ -28,7 +28,7 @@ RSpec.shared_context "cdn registry stubs" do
"images/apple-touch-icon.png" => "images/apple-touch-icon-1234.png"
},
webpack: {
"main" => ["main-1234.js"]
"main" => "main-1234.js"
}
)
)

View File

@ -18,7 +18,7 @@
const path = require('path')
const glob = require('glob')
const { DefinePlugin, EnvironmentPlugin } = require('webpack')
const { DefinePlugin, EnvironmentPlugin, ProvidePlugin } = require('webpack')
const partitioning = require('./partitioning')
const PluginSpecsRunner = require('./PluginSpecsRunner')
const { canvasDir } = require('#params')
@ -46,6 +46,25 @@ module.exports = {
require.resolve('tinymce'),
],
rules: [
{
test: /\.m?js$/,
type: 'javascript/auto',
include: [
path.resolve(canvasDir, 'node_modules/graphql'),
path.resolve(canvasDir, 'packages/datetime-moment-parser/index.js'),
path.resolve(canvasDir, 'packages/datetime/index.js'),
],
resolve: {
fullySpecified: false
}
},
{
test: /\.js$/,
type: 'javascript/auto',
include: [
path.resolve(canvasDir, 'node_modules/@instructure'),
]
},
{
test: /\.(js|ts|tsx)$/,
include: [
@ -63,6 +82,9 @@ module.exports = {
/gems\/plugins\/.*\/app\/(jsx|coffeescripts)\//
],
exclude: [/node_modules/],
parser: {
requireInclude: 'allow'
},
use: {
loader: 'babel-loader',
options: {
@ -166,6 +188,18 @@ module.exports = {
[CONTEXT_COFFEESCRIPT_SPEC]: path.join(canvasDir, CONTEXT_COFFEESCRIPT_SPEC),
[CONTEXT_EMBER_GRADEBOOK_SPEC]: path.join(canvasDir, CONTEXT_EMBER_GRADEBOOK_SPEC),
[CONTEXT_JSX_SPEC]: path.join(canvasDir, CONTEXT_JSX_SPEC),
// need to explicitly point this out for whatwg-url otherwise you get an
// error like:
//
// TypeError: Cannot read properties of undefined (reading 'decode')
//
// I suspect it's trying to use node's native impl and that doesn't work
// when run through webpack
['punycode']: path.join(canvasDir, 'node_modules/punycode/punycode.js'),
},
fallback: {
path: false, // for minimatch
},
extensions: ['.mjs', '.js', '.ts', '.tsx', '.coffee'],
modules: [
@ -189,7 +223,8 @@ module.exports = {
RESOURCE_COFFEESCRIPT_SPEC,
RESOURCE_EMBER_GRADEBOOK_SPEC,
RESOURCE_JSX_SPEC,
WEBPACK_PLUGIN_SPECS: JSON.stringify(WEBPACK_PLUGIN_SPECS)
WEBPACK_PLUGIN_SPECS: JSON.stringify(WEBPACK_PLUGIN_SPECS),
process: { env: {} },
}),
new EnvironmentPlugin({
@ -205,6 +240,12 @@ module.exports = {
pattern: 'gems/plugins/*/spec_canvas/coffeescripts/**/*Spec.js',
outfile: WEBPACK_PLUGIN_SPECS
}),
// needed for modules that expect Buffer to be present like fetch-mock (or
// whatwg-url, its dependency)
new ProvidePlugin({
'Buffer': ['buffer', 'Buffer']
}),
].concat(
process.env.JSPEC_GROUP ? [
partitioning.createPlugin({

View File

@ -24,11 +24,11 @@ const MomentTimezoneDataPlugin = require('moment-timezone-data-webpack-plugin')
const path = require('path')
const glob = require('glob')
const webpack = require('webpack')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin
const {WebpackManifestPlugin} = require('webpack-manifest-plugin')
const WebpackHooks = require('./webpackHooks')
const SourceFileExtensionsPlugin = require('./SourceFileExtensionsPlugin')
const EncapsulationPlugin = require('webpack-encapsulation-plugin')
// TODO: upgrade to webpack 5
// const EncapsulationPlugin = require('webpack-encapsulation-plugin')
const IgnoreErrorsPlugin = require('./IgnoreErrorsPlugin')
const webpackPublicPath = require('./webpackPublicPath')
const {canvasDir} = require('#params')
@ -55,6 +55,7 @@ const createBundleAnalyzerPlugin = (...args) => {
module.exports = {
mode: process.env.NODE_ENV,
target: ['web', 'es2021'],
performance: skipSourcemaps
? false
: {
@ -72,13 +73,10 @@ module.exports = {
maxAssetSize: 1400000
},
optimization: {
// concatenateModules: false, // uncomment if you want to get more accurate stuff from `yarn webpack:analyze`
moduleIds: 'hashed',
moduleIds: 'deterministic',
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: !skipSourcemaps,
terserOptions: {
compress: {
sequences: false, // prevents it from combining a bunch of statements with ","s so it is easier to set breakpoints
@ -109,6 +107,7 @@ module.exports = {
}
})
],
splitChunks: {
name: false,
@ -117,8 +116,8 @@ module.exports = {
maxInitialRequests: 10,
chunks: 'all',
cacheGroups: {vendors: false} // don't split out node_modules and app code in different chunks
}
cacheGroups: {defaultVendors: false} // don't split out node_modules and app code in different chunks
},
},
// In prod build, don't attempt to continue if there are any errors.
bail: process.env.NODE_ENV === 'production',
@ -133,11 +132,8 @@ module.exports = {
entry: {main: path.resolve(canvasDir, 'ui/index.js')},
output: {
// NOTE: hashSalt was added when HashedModuleIdsPlugin was installed, since
// chunkhashes are insensitive to moduleid changes. It should be changed again
// if this plugin is reconfigured or removed, or if there is another reason to
// prevent previously cached assets from being mixed with those from the new build
hashSalt: '2019-04-19',
publicPath: '',
clean: true,
path: path.join(canvasDir, 'public', webpackPublicPath),
// Add /* filename */ comments to generated require()s in the output.
@ -146,7 +142,6 @@ module.exports = {
// "e" is for "entry" and "c" is for "chunk"
filename: '[name]-e-[chunkhash:10].js',
chunkFilename: '[name]-c-[chunkhash:10].js',
jsonpFunction: 'canvasWebpackJsonp'
},
resolveLoader: {
@ -154,6 +149,12 @@ module.exports = {
},
resolve: {
fallback: {
// for minimatch module; it can work without path so let webpack know
// instead of trying to resolve node's "path"
path: false
},
modules: [
path.resolve(canvasDir, 'ui/shims'),
path.resolve(canvasDir, 'public/javascripts'),
@ -165,6 +166,14 @@ module.exports = {
},
module: {
parser: {
javascript: {
exportsPresence: 'error',
importExportsPresence: 'error',
reexportExportsPresence: 'error',
},
},
// This can boost the performance when ignoring big libraries.
// The files are expected to have no call to require, define or similar.
// They are allowed to use exports and module.exports.
@ -175,23 +184,66 @@ module.exports = {
require.resolve('tinymce'),
],
rules: [
// packages that do specify "type": "module" for their package but are
// still using non-fully qualified relative imports (e.g. "./foo"
// instead of "./foo.js") are rejected by webpack 5, and this works
// around it in the meantime
//
// to reproduce in the future, disable this rule block and verify that
// webpack compiles successfully without errors like:
//
// BREAKING CHANGE: The request '../jsutils/inspect' failed to
// resolve only because it was resolved as fully specified
//
// refs: https://github.com/webpack/webpack/issues/11467#issuecomment-691873586
// https://github.com/babel/babel/issues/12058
// https://github.com/graphql/graphql-js/issues/2721
{
test: /\.m?js$/,
type: 'javascript/auto',
include: [
path.resolve(canvasDir, 'node_modules/graphql'),
path.resolve(canvasDir, 'packages/datetime-moment-parser/index.js'),
path.resolve(canvasDir, 'packages/datetime/index.js'),
],
resolve: {
fullySpecified: false
}
},
// remove when you no longer get an error from @instructure/ui* around
// import/export with a package not marked as ESM:
//
// ERROR in ./node_modules/@instructure/ui-view/es/index.js 24:0
// Module parse failed: 'import' and 'export' may appear only with 'sourceType: module' (24:0)
// You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
//
{
test: /\.js$/,
type: 'javascript/auto',
include: [
path.resolve(canvasDir, 'node_modules/@instructure'),
]
},
{
test: /\.(js|ts|tsx)$/,
include: [
path.resolve(canvasDir, 'ui'),
path.resolve(canvasDir, 'packages/jquery-kyle-menu'),
path.resolve(canvasDir, 'packages/jquery-sticky'),
path.resolve(canvasDir, 'packages/jquery-popover'),
path.resolve(canvasDir, 'packages/jquery-selectmenu'),
path.resolve(canvasDir, 'packages/jquery-sticky'),
path.resolve(canvasDir, 'packages/mathml'),
path.resolve(canvasDir, 'packages/persistent-array'),
path.resolve(canvasDir, 'packages/slickgrid'),
path.resolve(canvasDir, 'packages/with-breakpoints'),
path.resolve(canvasDir, 'spec/javascripts/jsx'),
path.resolve(canvasDir, 'spec/coffeescripts'),
/gems\/plugins\/.*\/app\/(jsx|coffeescripts)\//
...globPlugins('app/{jsx,coffeescripts}/'),
],
exclude: [/bower\//, /node_modules/],
exclude: [/node_modules/],
parser: {
requireInclude: 'allow'
},
use: {
loader: 'babel-loader',
options: {
@ -240,14 +292,17 @@ module.exports = {
path.resolve(canvasDir, 'spec/coffeescripts'),
path.resolve(canvasDir, 'packages/backbone-input-filter-view/src'),
path.resolve(canvasDir, 'packages/backbone-input-view/src'),
/gems\/plugins\/.*\/(app|spec_canvas)\/coffeescripts\//
...globPlugins('{app,spec_canvas}/coffeescripts/')
],
loaders: ['coffee-loader']
use: ['coffee-loader']
},
{
test: /\.handlebars$/,
include: [path.resolve(canvasDir, 'ui'), /gems\/plugins\/.*\/app\/views\/jst\//],
loaders: [
include: [
path.resolve(canvasDir, 'ui'),
...globPlugins('app/views/jst/'),
],
use: [
{
loader: require.resolve('./i18nLinerHandlebars'),
options: {
@ -260,7 +315,7 @@ module.exports = {
{
test: /\.hbs$/,
include: [path.join(canvasDir, 'ui/features/screenreader_gradebook/jst')],
loaders: [require.resolve('./emberHandlebars')]
use: [require.resolve('./emberHandlebars')]
},
{
test: /\.css$/,
@ -303,25 +358,18 @@ module.exports = {
new WebpackHooks(),
// avoids warnings caused by
// https://github.com/graphql/graphql-language-service/issues/111, should
// be removed when that issue is fixed
new webpack.IgnorePlugin(/\.flow$/),
new CleanWebpackPlugin(),
new EncapsulationPlugin({
test: /\.[tj]sx?$/,
include: [
path.resolve(canvasDir, 'ui'),
path.resolve(canvasDir, 'packages'),
path.resolve(canvasDir, 'public/javascripts'),
path.resolve(canvasDir, 'gems/plugins')
],
exclude: [/\/node_modules\//],
formatter: require('./encapsulation/ErrorFormatter'),
rules: require('./encapsulation/moduleAccessRules')
}),
// new EncapsulationPlugin({
// test: /\.[tj]sx?$/,
// include: [
// path.resolve(canvasDir, 'ui'),
// path.resolve(canvasDir, 'packages'),
// path.resolve(canvasDir, 'public/javascripts'),
// path.resolve(canvasDir, 'gems/plugins')
// ],
// exclude: [/\/node_modules\//],
// formatter: require('./encapsulation/ErrorFormatter'),
// rules: require('./encapsulation/moduleAccessRules')
// }),
new IgnoreErrorsPlugin({
errors: require('./encapsulation/errorsPendingRemoval.json'),
@ -329,7 +377,15 @@ module.exports = {
}),
new webpack.DefinePlugin({
CANVAS_WEBPACK_PUBLIC_PATH: JSON.stringify(webpackPublicPath)
CANVAS_WEBPACK_PUBLIC_PATH: JSON.stringify(webpackPublicPath),
NODE_ENV: null,
// webpack5 stopped providing a polyfill for process.env and its use in
// web code is discouraged but a number of our dependencies still rely on
// this, so we either selectively shim every property that they may be
// referencing through the EnvironmentPlugin (below) and risk a hard
// runtime error in case we didn't cover them all, or provide a sink like
// this, which i'm gonna go with for now:
process: { env: {} },
})
]
.concat(
@ -368,24 +424,22 @@ module.exports = {
process.env.NODE_ENV === 'test'
? []
: [
// don't include any of the moment locales in the common bundle (otherwise it is huge!)
// we load them explicitly onto the page in include_js_bundles from rails.
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// don't include any of the moment locales in the common bundle
// (otherwise it is huge!) we load them explicitly onto the page in
// include_js_bundles from rails.
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
}),
// outputs a json file so Rails knows which hash fingerprints to add
// to each script url and so it knows which split chunks to make a
// <link rel=preload ... /> for for each `js_bundle`
new StatsWriterPlugin({
filename: 'webpack-manifest.json',
fields: ['namedChunkGroups'],
transform(data) {
const res = {}
Object.entries(data.namedChunkGroups).forEach(([key, value]) => {
res[key] = value.assets.filter(a => a.endsWith('.js'))
})
return JSON.stringify(res, null, 2)
}
})
new WebpackManifestPlugin({
fileName: 'webpack-manifest.json',
publicPath: '',
useEntryKeys: true
}),
]
)
}
@ -406,7 +460,7 @@ if (process.env.CRYSTALBALL_MAP === '1') {
path.resolve(canvasDir, 'packages/with-breakpoints'),
path.resolve(canvasDir, 'spec/javascripts/jsx'),
path.resolve(canvasDir, 'spec/coffeescripts'),
/gems\/plugins\/.*\/app\/(jsx|coffeescripts)\//
...globPlugins('app/{jsx,coffeescripts}/'),
],
exclude: [/test\//, /spec/],
use: {
@ -416,3 +470,10 @@ if (process.env.CRYSTALBALL_MAP === '1') {
enforce: 'post'
})
}
function globPlugins(pattern) {
return glob.sync(`gems/plugins/*/${pattern}`, {
absolute: true,
cwd: canvasDir
})
}

View File

@ -15,9 +15,7 @@
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const exec = require('child_process').exec
module.exports = class WebpackHooks {
apply(compiler) {
const isEnabled = JSON.parse(process.env.ENABLE_CANVAS_WEBPACK_HOOKS || 'false')
@ -27,18 +25,15 @@ module.exports = class WebpackHooks {
CANVAS_WEBPACK_FAILED_HOOK,
CANVAS_WEBPACK_DONE_HOOK
} = process.env
if (CANVAS_WEBPACK_START_HOOK) {
compiler.plugin('compile', () => exec(CANVAS_WEBPACK_START_HOOK))
compiler.hooks.compile.tap('Canvas:WebpackHooks', () => exec(CANVAS_WEBPACK_START_HOOK))
}
if (CANVAS_WEBPACK_FAILED_HOOK) {
compiler.plugin('failed', () => exec(CANVAS_WEBPACK_FAILED_HOOK))
compiler.hooks.failed.tap('Canvas:WebpackHooks', () => exec(CANVAS_WEBPACK_FAILED_HOOK))
}
if (CANVAS_WEBPACK_DONE_HOOK) {
compiler.plugin('done', () => exec(CANVAS_WEBPACK_DONE_HOOK))
compiler.hooks.done.tap('Canvas:WebpackHooks', () => exec(CANVAS_WEBPACK_DONE_HOOK))
}
}
}
}
}

592
yarn.lock

File diff suppressed because it is too large Load Diff