Replace webpack+babel with esbuild (#6230)

* Replace webpack+babel with esbuild
This commit is contained in:
Pomax 2021-02-23 10:44:11 -08:00 коммит произвёл GitHub
Родитель 83e163afb4
Коммит fb8b2d7faf
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 463 добавлений и 4263 удалений

52
esbuild.config.js Normal file
Просмотреть файл

@ -0,0 +1,52 @@
/**
* This is not technically a "config", but the actual run script that
* invokes ESBuild (https://esbuild.github.io) on the various libraries
* that we need bundled and written to our frontend's js directory.
*
* As a normal script, this should be invoked using node, and it can
* take a runtime argument `--node-env` followed by either the string
* "production" or "development" so that process.env.NODE_ENV in our
* code gets interpreted correctly.
*/
const { build } = require(`esbuild`);
const path = require(`path`);
const arg = process.argv.indexOf(`--node-env`);
const mode =
arg > 0 ? process.argv[arg + 1] : process.env.NODE_ENV || `development`;
const inProduction = mode === `production`;
const inDir = `./source/js/`;
const outDir = `./network-api/networkapi/frontend/_js/`;
const sources = [
`main.js`,
`foundation/pages/mozfest/index.js`,
`foundation/pages/directory-listing-filters.js`,
`buyers-guide/bg-main.js`,
`polyfills.js`,
];
const base = {
bundle: true,
watch: !inProduction,
sourcemap: !inProduction,
minify: inProduction,
loader: {
".js": "jsx",
".jsx": "jsx",
},
define: {
"process.env.NODE_ENV": JSON.stringify(mode),
},
inject: [`esbuild.react.shim.js`],
};
sources.map((source) => {
const opts = Object.assign({}, base);
opts.entryPoints = [`${inDir}${source}`];
opts.outfile = `${outDir}${path
.basename(source)
.replace(`.js`, `.compiled.js`)}`;
return build(opts);
});

12
esbuild.react.shim.js Normal file
Просмотреть файл

@ -0,0 +1,12 @@
import * as React from "react";
/**
* In order for ESBuild to correctly resolve the React.createElement()
* function that babel "auto injects" when it transfroms JSX, we need
* the following shim that ensures that any file that relies on React
* has the variable "React" available for use.
*
* See https://esbuild.github.io/content-types/#auto-import-for-jsx
*/
export { React };

Просмотреть файл

@ -1,179 +1,192 @@
///////////////
jQuery( () => {
///////////////
jQuery(() => {
///////////////
const tabbedContent = $(`form .tab-content`);
const topLevel = (tabbedContent.length > 0) ? tabbedContent.first() : $(`.content form`);
const languageCodeRegex = new RegExp(' \\[('+wagtailModelTranslations.languages.join('|')+')\\]');
const tabbedContent = $(`form .tab-content`);
const topLevel =
tabbedContent.length > 0 ? tabbedContent.first() : $(`.content form`);
const languageCodeRegex = new RegExp(
" \\[(" + wagtailModelTranslations.languages.join("|") + ")\\]"
);
if (topLevel.length === 0) {
// obviously, if we don't have an element to attach
// the picker to, we might as well stop right now.
return;
if (topLevel.length === 0) {
// obviously, if we don't have an element to attach
// the picker to, we might as well stop right now.
return;
}
if (topLevel.attr(`class`) && topLevel.attr(`class`).indexOf(`search`) > -1) {
// if the only forms on the page are search forms,
// we're not actually dealing with page/snippets
return;
}
/**
* ...
*/
function filterForLocale(index, element) {
var tc = element.textContent;
var res = languageCodeRegex.exec(tc);
if (res === null) return;
var code = res[0],
locale = code.replace(/[ \[\]]/g, "");
// Verify this is a known locale and not a fluke,
// using the global "wagtailModelTranslations.languages"
// variable, which is an
// array of all language codes specified in the
// settings.LANGUAGES variable for Django.
if (wagtailModelTranslations.languages.indexOf(locale) === -1) return;
// Check if it's a LI for inlines
if ($(element).has("ul.multiple").length) return;
// We do our show/hiding based on list items,
// otherwise we're just "emptying" a list item
// while leaving its spacing CSS intact.
if (element.nodeName !== "LI") {
element = $(element).closest("li")[0];
}
if (topLevel.attr(`class`) && topLevel.attr(`class`).indexOf(`search`) > -1) {
// if the only forms on the page are search forms,
// we're not actually dealing with page/snippets
return;
// Bootstrap an empty bin if we don't have one.
if (!localisedElements[locale]) {
localisedElements[locale] = [];
}
/**
* ...
*/
function filterForLocale(index, element) {
var tc = element.textContent;
var res = languageCodeRegex.exec(tc);
if (res === null) return;
// Add this element to our bin, provided it had
// not already been added.
var bin = localisedElements[locale];
if (bin.indexOf(element) === -1) {
bin.push(element);
element.classList.add(`l10n-hidden`);
var code = res[0],
locale = code.replace(/[ \[\]]/g,'');
// Verify this is a known locale and not a fluke,
// using the global "wagtailModelTranslations.languages"
// variable, which is an
// array of all language codes specified in the
// settings.LANGUAGES variable for Django.
if (wagtailModelTranslations.languages.indexOf(locale) === -1) return;
// Check if it's a LI for inlines
if ($(element).has('ul.multiple').length) return;
// We do our show/hiding based on list items,
// otherwise we're just "emptying" a list item
// while leaving its spacing CSS intact.
if (element.nodeName !== "LI") {
element = $(element).closest("li")[0];
}
// Bootstrap an empty bin if we don't have one.
if (!localisedElements[locale]) {
localisedElements[locale] = [];
}
// Add this element to our bin, provided it had
// not already been added.
var bin = localisedElements[locale];
if (bin.indexOf(element) === -1) {
bin.push(element);
element.classList.add(`l10n-hidden`);
// also note that "field-col" elements may now look horribly
// wrong, due to how Wagtail computes which of "col3"..."col12"
// to use. Because wagtail-modeltranslation introduces many more
// elements to show in an "inline" element, things that were
// "col6" before end up being "col1", looking terribly wrong indeed.
element.classList.remove(...columnCSS);
}
// also note that "field-col" elements may now look horribly
// wrong, due to how Wagtail computes which of "col3"..."col12"
// to use. Because wagtail-modeltranslation introduces many more
// elements to show in an "inline" element, things that were
// "col6" before end up being "col1", looking terribly wrong indeed.
element.classList.remove(...columnCSS);
}
}
/**
* Build the set of fields-per-locale. Each set will receive
* a button to toggle visibility for all fields in that set,
* with the note that unlocalised content (such as images)
* will always stay visible.
*/
function buildSets(topElement) {
$(`li.object:not(.multi-field), div.field`, topElement).each( filterForLocale );
document.dispatchEvent(new CustomEvent('wagtail-modeltranslation:buildSets:done'));
}
/**
* Build the set of fields-per-locale. Each set will receive
* a button to toggle visibility for all fields in that set,
* with the note that unlocalised content (such as images)
* will always stay visible.
*/
function buildSets(topElement) {
$(`li.object:not(.multi-field), div.field`, topElement).each(
filterForLocale
);
document.dispatchEvent(
new CustomEvent("wagtail-modeltranslation:buildSets:done")
);
}
/**
* Gets all selected locales as array of strings
*/
function getSelectedLocales() {
var selectedLocales = [];
$('.showing-locale').each(function() {
selectedLocales.push($(this).text());
});
return selectedLocales;
}
/**
* Adds event listeners on click for inlines.
*/
$('a[id^=id_][id$=-ADD]').click(e => {
e.preventDefault();
// get the ul where all inlines will be added
ulForms = $(e.currentTarget).parent().siblings('ul.multiple')[0];
// register new fields in sets
buildSets(ulForms);
// remove visibility class for selected locales
selectedLocales = getSelectedLocales();
selectedLocales.forEach( locale => {
toggleLocale(locale, true);
});
/**
* Gets all selected locales as array of strings
*/
function getSelectedLocales() {
var selectedLocales = [];
$(".showing-locale").each(function () {
selectedLocales.push($(this).text());
});
return selectedLocales;
}
/**
* Build a locale picker bar, with buttons that toggle
* visibility for each locale's fields.
*/
function buildLocaleToggler() {
var bar = $(`<div class="locale-picker"><h2>${wagtailModelTranslations.viewEditString}</h2></div>`);
var ul = $(`<ul class="locales"></ul>`);
bar.append(ul);
/**
* Adds event listeners on click for inlines.
*/
$("a[id^=id_][id$=-ADD]").click((e) => {
e.preventDefault();
// get the ul where all inlines will be added
ulForms = $(e.currentTarget).parent().siblings("ul.multiple")[0];
// register new fields in sets
buildSets(ulForms);
// remove visibility class for selected locales
selectedLocales = getSelectedLocales();
selectedLocales.forEach((locale) => {
toggleLocale(locale, true);
});
});
var toggles = {};
locales.forEach( locale => {
var li = $(`<li class="locale"><button class="locale-toggle">${locale}</button></li>`);
ul.append(li);
/**
* Build a locale picker bar, with buttons that toggle
* visibility for each locale's fields.
*/
function buildLocaleToggler() {
var bar = $(
`<div class="locale-picker"><h2>${wagtailModelTranslations.viewEditString}</h2></div>`
);
var ul = $(`<ul class="locales"></ul>`);
bar.append(ul);
$(`button.locale-toggle`, li).each( (index, toggle) => {
toggle.addEventListener(`click`, e => {
e.preventDefault();
toggle.classList.toggle(`showing-locale`);
toggleLocale(locale);
});
var toggles = {};
locales.forEach((locale) => {
var li = $(
`<li class="locale"><button class="locale-toggle">${locale}</button></li>`
);
ul.append(li);
toggles[locale] = toggle;
$(`button.locale-toggle`, li).each((index, toggle) => {
toggle.addEventListener(`click`, (e) => {
e.preventDefault();
toggle.classList.toggle(`showing-locale`);
toggleLocale(locale);
});
toggles[locale] = toggle;
});
bar.prependTo(topLevel);
return toggles;
}
/**
* This function allows either blind toggling
* of a field's visibility, or explicitly
* making visible/invisible based on the
* value of `state` (a boolean).
*/
function toggleLocale(locale, state) {
var action = `toggle`;
if (state !== undefined) {
action = state ? `remove` : `add`
}
localisedElements[locale].forEach(element => {
element.classList[action](`l10n-hidden`);
});
}
var default_locale = wagtailModelTranslations.defaultLanguage;
var localisedElements = {};
var columnCSS = [`field-col`];
for (var i=1; i<=12; i++) { columnCSS.push(`col${i}`); }
// Build the sets that track which fields
// belong to which language code.
buildSets(topLevel);
var locales = Object.keys(localisedElements).sort();
// If there are no locale sets, then there is
// no locale field picker to build, either.
if (locales.length === 0) return;
// If there are locale sets, make sure to
// enable at least the default locale after
// building and hiding all locale sets.
var localeToggler = buildLocaleToggler();
localeToggler[default_locale].click();
///////////////
});
///////////////
bar.prependTo(topLevel);
return toggles;
}
/**
* This function allows either blind toggling
* of a field's visibility, or explicitly
* making visible/invisible based on the
* value of `state` (a boolean).
*/
function toggleLocale(locale, state) {
var action = `toggle`;
if (state !== undefined) {
action = state ? `remove` : `add`;
}
localisedElements[locale].forEach((element) => {
element.classList[action](`l10n-hidden`);
});
}
var default_locale = wagtailModelTranslations.defaultLanguage;
var localisedElements = {};
var columnCSS = [`field-col`];
for (var i = 1; i <= 12; i++) {
columnCSS.push(`col${i}`);
}
// Build the sets that track which fields
// belong to which language code.
buildSets(topLevel);
var locales = Object.keys(localisedElements).sort();
// If there are no locale sets, then there is
// no locale field picker to build, either.
if (locales.length === 0) return;
// If there are locale sets, make sure to
// enable at least the default locale after
// building and hiding all locale sets.
var localeToggler = buildLocaleToggler();
localeToggler[default_locale].click();
///////////////
});
///////////////

Просмотреть файл

@ -38,12 +38,16 @@ sync.addEventListener(`click`, () => {
// there any errors? If so, we need to immediately reveal all fields.
if (document.querySelectorAll(`p.error-message`).length !== 0) {
showAll.click();
document.dispatchEvent(new CustomEvent(`wagtailcustomization:copy-helper:done`));
document.dispatchEvent(
new CustomEvent(`wagtailcustomization:copy-helper:done`)
);
}
// If we kicked in before the localization interface JS kicked in: listen for
// its "I am done" event so we can reveal fields once it's safe to do so.
document.addEventListener(`wagtail-modeltranslation:buildSets:done`, evt => {
document.addEventListener(`wagtail-modeltranslation:buildSets:done`, (evt) => {
showAll.click();
document.dispatchEvent(new CustomEvent(`wagtailcustomization:copy-helper:done`));
document.dispatchEvent(
new CustomEvent(`wagtailcustomization:copy-helper:done`)
);
});

4158
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -6,16 +6,17 @@
"node": ">=14.4.0"
},
"scripts": {
"build:common": "shx rm -rf network-api/networkapi/frontend && shx mkdir -p network-api/networkapi/frontend/_js && run-p build:images build:sass && npm run build:contribute",
"build": "run-s build:clean && run-p build:js:prod build:common",
"build:clean": "shx rm -rf network-api/networkapi/frontend && shx mkdir -p network-api/networkapi/frontend/_js",
"build:common": "run-p build:images build:sass build:contribute",
"build:contribute": "shx cp contribute.json network-api/networkapi/frontend/",
"build:dev": "run-s build:common build:js:dev",
"build:images": "shx rm -rf network-api/networkapi/frontend/_images && shx cp -r source/images network-api/networkapi/frontend/_images",
"build:js-uncompressed": "npm run build:js:dev",
"build:js": "webpack --mode=production",
"build:js:dev": "webpack --mode=development",
"build:sass": "shx mkdir -p network-api/networkapi/frontend/_css && sass source/sass/main.scss network-api/networkapi/frontend/_css/main.compiled.css && sass source/sass/buyers-guide/bg-main.scss network-api/networkapi/frontend/_css/buyers-guide.compiled.css && npm run optimize:css",
"build": "run-s build:common build:js",
"build-uncompressed": "run-s build:common build:js-uncompressed",
"build:js:dev": "node esbuild.config.js -- --node-env development",
"build:js:prod": "node esbuild.config.js -- --node-env production",
"build:sass": "run-s build:sass:clean && run-p build:sass:main build:sass:bg && run-s optimize:css",
"build:sass:clean": "shx mkdir -p network-api/networkapi/frontend/_css",
"build:sass:main": "sass source/sass/main.scss network-api/networkapi/frontend/_css/main.compiled.css",
"build:sass:bg": "sass source/sass/buyers-guide/bg-main.scss network-api/networkapi/frontend/_css/buyers-guide.compiled.css",
"cypress": "run-p --race cypress:docker cypress:test",
"cypress:a11y": "run-p --race cypress:docker cypress:test:a11y",
"cypress:ci": "run-p --race server cypress:test",
@ -29,19 +30,21 @@
"fix:js": "npm run test:eslint:js -- --fix",
"fix:scss": "npm run test:scss -- --fix",
"heroku-postbuild": "npm run build",
"optimize:css": "postcss network-api/networkapi/frontend/_css/*.css --dir network-api/networkapi/frontend/_css",
"optimize:css": "run-s optimize:css:*",
"optimize:css:run": "postcss network-api/networkapi/frontend/_css/*.css --dir network-api/networkapi/frontend/_css/temp",
"optimize:css:copy": "shx mv network-api/networkapi/frontend/_css/temp/* network-api/networkapi/frontend/_css",
"optimize:css:clean": "shx rm -rf network-api/networkapi/frontend/_css/temp",
"optimize:svg": "svgo --multipass --folder ./source/images/ --recursive",
"optimize:jpg": "find source/images -type f -name '*.jpg' -print0 | xargs -0 -n 1 -P 6 -I '{}' guetzli --quality 93 '{}' '{}'",
"optimize:png": "find source/images -type f -name '*.png' -print0 | xargs -0 -n 1 -P 6 optipng",
"optimize": "run-p optimize:**",
"percy": "percy exec -t 800 -- npm run cypress:ci",
"precommit": "prettier \"source/js/**/*.js\" \"source/js/**/*.jsx\" webpack.config.js --write",
"prestatus": "echo test",
"precommit": "prettier \"source/js/**/*.js\" \"source/js/**/*.jsx\" *.config.js --write",
"server": "python network-api/manage.py runserver 0.0.0.0:8000",
"start": "docker-compose up",
"test:procfile": "node test/test-procfile.js",
"test:eslint:js:pre": "prettier \"cypress/integration/*.js\" \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" webpack.config.js --write",
"test:eslint:js": "eslint --config ./.eslintrc.json \"cypress/integration/*.js\" \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" webpack.config.js",
"test:eslint:js:pre": "prettier \"cypress/integration/*.js\" \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" ./*.js --write",
"test:eslint:js": "eslint --config ./.eslintrc.json \"cypress/integration/*.js\" \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" ./*.js",
"test:eslint:a11y:pre": "prettier \"source/js/**/*.jsx\" --write",
"test:eslint:a11y": "eslint --config ./.eslintrc.a11y.json \"source/js/**/*.jsx\"",
"test:scss:pre": "prettier \"source/sass/**/*.scss\" \"source/js/**/*.scss\" \"network-api/networkapi/{,!(frontend)/**/}*.css\" --write",
@ -49,9 +52,8 @@
"test:scss:styleguide:color": "stylelint \"source/sass/**/*.scss\" \"source/js/**/*.scss\" \"!source/sass/**/_colors.scss\" --syntax scss --config .stylelintrc-colors.js",
"test:css": "stylelint \"network-api/networkapi/{,!(frontend)/**/}*.css\" --syntax scss",
"test": "run-s test:** build",
"watch": "npm run build:dev && run-p watch:**",
"watch": "run-s build:clean && run-p build:js:dev build:common watch:**",
"watch:images": "chokidar \"source/images/**/*\" -c \"npm run build:images\"",
"watch:js": "chokidar \"source/js/**/*.js\" \"source/js/**/*.jsx\" -c \"npm run build:js-uncompressed\"",
"watch:sass": "chokidar \"source/**/*.scss\" -c \"npm run build:sass\""
},
"browserslist": [
@ -61,17 +63,13 @@
"author": "Mozilla",
"license": "MPL-2.0",
"dependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.13.0",
"@babel/preset-react": "^7.12.10",
"@sentry/browser": "^6.0.3",
"autoprefixer": "^10.2.4",
"axe-core": "^4.1.1",
"babel-core": "^6.26.3",
"babel-loader": "^8.2.2",
"bootstrap": "^4.6.0",
"classnames": "2.2.6",
"cssnano": "^4.1.10",
"esbuild": "^0.8.46",
"event-stream": "3.3.4",
"js-cookie": "2.2.1",
"moment": "^2.29.1",
@ -86,8 +84,6 @@
"sass": "^1.32.5",
"shx": "^0.3.3",
"uuid": "^8.3.2",
"webpack": "^5.19.0",
"webpack-cli": "^4.5.0",
"whatwg-fetch": "^3.5.0"
},
"devDependencies": {

Просмотреть файл

@ -1,17 +1,21 @@
let commonPlugins = [
require('autoprefixer'),
];
let commonPlugins = [require("autoprefixer")];
let prodPlugins = [
require('cssnano')({
preset: ['default', {
require("cssnano")({
preset: [
"default",
{
discardComments: {
removeAll: true,
removeAll: true,
},
}]
})
},
],
}),
];
module.exports = {
plugins: process.env.NODE_ENV === 'production' ? commonPlugins.concat(prodPlugins) : commonPlugins
plugins:
process.env.NODE_ENV === "production"
? commonPlugins.concat(prodPlugins)
: commonPlugins,
};

Просмотреть файл

@ -76,7 +76,7 @@ export default class CreepVote extends Component {
// If we're running for the new wagtail version of PNI, voting is a post to the product's URL instead.
if (document.getElementById("product-research").dataset.isWagtailPage) {
url = ".";
url = ".";
}
let method = `POST`;
let credentials = `same-origin`;

Просмотреть файл

@ -9,18 +9,16 @@ export default (apps) => {
const targetNode = document.querySelector(
`#multipage-nav-mobile .container .row .col-12`
);
const multipageLinks = document.querySelectorAll(`#multipage-nav a`)
const multipageLinks = document.querySelectorAll(`#multipage-nav a`);
if (targetNode && multipageLinks.length) {
let links = Array.from(multipageLinks).map(
(link) => {
return {
label: link.textContent.trim(),
href: link.getAttribute(`href`),
isActive: !!link.getAttribute(`class`).match(/active/),
};
}
);
let links = Array.from(multipageLinks).map((link) => {
return {
label: link.textContent.trim(),
href: link.getAttribute(`href`),
isActive: !!link.getAttribute(`class`).match(/active/),
};
});
apps.push(
new Promise((resolve) => {

Просмотреть файл

@ -3,10 +3,10 @@
*/
export default () => {
const summaryBlock = document.querySelector(".article-navbar-container");
if(summaryBlock) {
const marginOffset = window.getComputedStyle(summaryBlock).getPropertyValue(
"--top-offset"
);
if (summaryBlock) {
const marginOffset = window
.getComputedStyle(summaryBlock)
.getPropertyValue("--top-offset");
const dropDownMenu = document.querySelector(".article-summary-menu");
const articleSummaryToggle = document.querySelector(
".article-summary-toggle"
@ -33,7 +33,7 @@ export default () => {
}
},
{
passive: true
passive: true,
}
);
}

Просмотреть файл

@ -408,7 +408,11 @@
@include hover-focus-active {
/* stylelint-disable */
// This is a one-off color.
background: rgb(231, 231, 252); // One-off colors; no variable exists for this
background: rgb(
231,
231,
252
); // One-off colors; no variable exists for this
/* stylelint-enable */
text-decoration: none;

Просмотреть файл

@ -1,63 +0,0 @@
let webpack = require(`webpack`);
let path = require(`path`);
let frontendPath = path.resolve(
__dirname,
`network-api`,
`networkapi`,
`frontend`,
`_js`
);
let rules = [
{
test: /\.js(x?)$/,
exclude: /node_modules/,
loader: `babel-loader`,
options: {
presets: [
[`@babel/preset-env`, { targets: `> 1%, last 2 versions` }],
[`@babel/preset-react`, { runtime: "automatic" }],
],
},
},
];
let main = {
devtool: false,
entry: {
main: `./source/js/main.js`,
mozfest: `./source/js/foundation/pages/mozfest/index.js`,
"directory-listing-filters": `./source/js/foundation/pages/directory-listing-filters.js`,
},
output: {
path: frontendPath,
filename: `[name].compiled.js`,
},
module: {
rules,
},
plugins: [new webpack.EnvironmentPlugin(["NODE_ENV"])],
};
let bgMain = {
devtool: false,
entry: {
"bg-main": `./source/js/buyers-guide/bg-main.js`,
polyfills: `./source/js/polyfills.js`,
},
output: {
path: frontendPath,
filename: `[name].compiled.js`,
},
module: {
rules,
},
};
let config = [main, bgMain];
module.exports = (env, argv) => {
process.env.NODE_ENV = process.env.NODE_ENV || argv.mode;
return config;
};