DEV: Improve Ember module shims under Ember CLI (#15795) (#15806)

In our legacy environment, Ember RFC176 shims are included in `discourse-loader.js` which is part of the `vendor.js` bundle. This meant that the module shims were available as soon as the vendor.js asset was loaded.

Under Ember CLI, we were defining these shims in `discourse-boot.js`. This is loaded by the browser much later, and meant that the shims were not available to themes/plugins that call `require()` before Discourse has booted. This was causing errors under some circumstances.

This commit refactors the Ember CLI implementation so that the shims are included in the vendor.js bundle. This is done via an addon which leans on the ember-rfc176-data NPM package. This will ensure we have all the definitions, without the need for manual copy/paste.
This commit is contained in:
David Taylor 2022-02-03 17:36:32 +00:00 committed by GitHub
parent c985f82174
commit 569fa8a135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 161 deletions

View File

@ -110,6 +110,11 @@ module.exports = function (defaults) {
});
app.import(discourseRoot + "/app/assets/javascripts/polyfills.js");
app.import(
discourseRoot +
"/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js"
);
const mergedTree = mergeTrees([
discourseScss(`${discourseRoot}/app/assets/stylesheets`, "testem.scss"),
createI18nTree(discourseRoot, vendorJs),

View File

@ -0,0 +1 @@
engine-strict = true

View File

@ -0,0 +1,57 @@
"use strict";
// In core, babel-plugin-ember-modules-api-polyfill takes care of re-writing the new module
// syntax to the legacy Ember globals. For themes and plugins, we need to manually set up
// the modules.
//
// Eventually, Ember RFC176 will be implemented, and we can drop these shims.
const RFC176Data = require("ember-rfc176-data");
module.exports = {
name: require("./package").name,
isDevelopingAddon() {
return true;
},
contentFor: function (type) {
if (type !== "vendor-suffix") {
return;
}
const modules = {};
for (const entry of RFC176Data) {
// Entries look like:
// {
// global: 'Ember.expandProperties',
// module: '@ember/object/computed',
// export: 'expandProperties',
// deprecated: false
// },
if (entry.deprecated) {
continue;
}
let m = modules[entry.module];
if (!m) {
m = modules[entry.module] = [];
}
m.push(entry);
}
let output = "";
for (const moduleName of Object.keys(modules)) {
const exports = modules[moduleName];
const rawExports = exports
.map((e) => `${e.export}:${e.global}`)
.join(",");
output += `define("${moduleName}", () => {return {${rawExports}}});\n`;
}
return output;
},
};

View File

@ -0,0 +1,6 @@
{
"name": "rfc176-shims",
"keywords": [
"ember-addon"
]
}

View File

@ -49,6 +49,7 @@
"ember-load-initializers": "^2.1.1",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-qunit": "^5.1.2",
"ember-rfc176-data": "^0.3.17",
"ember-source": "~3.15.0",
"ember-test-selectors": "^6.0.0",
"eslint": "^7.27.0",
@ -78,7 +79,8 @@
},
"ember-addon": {
"paths": [
"lib/bootstrap-json"
"lib/bootstrap-json",
"lib/rfc176-shims"
]
},
"devDependencies": {

View File

@ -2,157 +2,6 @@
if (window.unsupportedBrowser) {
throw "Unsupported browser detected";
}
// TODO: These are needed to load plugins because @ember has its own loader.
// We should find a nicer way to do this.
const EMBER_MODULES = {
"@ember/application": {
default: Ember.Application,
setOwner: Ember.setOwner,
getOwner: Ember.getOwner,
},
"@ember/array": {
default: Ember.Array,
A: Ember.A,
isArray: Ember.isArray,
},
"@ember/array/proxy": {
default: Ember.ArrayProxy,
},
"@ember/component": {
default: Ember.Component,
},
"@ember/component/helper": {
default: Ember.Helper,
},
"@ember/component/text-field": {
default: Ember.TextField,
},
"@ember/component/text-area": {
default: Ember.TextArea,
},
"@ember/controller": {
default: Ember.Controller,
inject: Ember.inject.controller,
},
"@ember/debug": {
warn: Ember.warn,
},
"@ember/error": {
default: Ember.error,
},
"@ember/object": {
action: Ember._action,
default: Ember.Object,
get: Ember.get,
getProperties: Ember.getProperties,
set: Ember.set,
setProperties: Ember.setProperties,
computed: Ember.computed,
defineProperty: Ember.defineProperty,
},
"@ember/object/computed": {
alias: Ember.computed.alias,
and: Ember.computed.and,
bool: Ember.computed.bool,
collect: Ember.computed.collect,
deprecatingAlias: Ember.computed.deprecatingAlias,
empty: Ember.computed.empty,
equal: Ember.computed.equal,
filter: Ember.computed.filter,
filterBy: Ember.computed.filterBy,
gt: Ember.computed.gt,
gte: Ember.computed.gte,
intersect: Ember.computed.intersect,
lt: Ember.computed.lt,
lte: Ember.computed.lte,
map: Ember.computed.map,
mapBy: Ember.computed.mapBy,
match: Ember.computed.match,
max: Ember.computed.max,
min: Ember.computed.min,
none: Ember.computed.none,
not: Ember.computed.not,
notEmpty: Ember.computed.notEmpty,
oneWay: Ember.computed.oneWay,
or: Ember.computed.or,
readOnly: Ember.computed.readOnly,
reads: Ember.computed.reads,
setDiff: Ember.computed.setDiff,
sort: Ember.computed.sort,
sum: Ember.computed.sum,
union: Ember.computed.union,
uniq: Ember.computed.uniq,
uniqBy: Ember.computed.uniqBy,
},
"@ember/object/internals": {
guidFor: Ember.guidFor,
},
"@ember/object/mixin": { default: Ember.Mixin },
"@ember/object/proxy": { default: Ember.ObjectProxy },
"@ember/object/promise-proxy-mixin": { default: Ember.PromiseProxyMixin },
"@ember/object/evented": {
default: Ember.Evented,
on: Ember.on,
},
"@ember/routing/route": { default: Ember.Route },
"@ember/routing/router": { default: Ember.Router },
"@ember/runloop": {
bind: Ember.run.bind,
cancel: Ember.run.cancel,
debounce: Ember.testing ? Ember.run : Ember.run.debounce,
later: Ember.run.later,
next: Ember.run.next,
once: Ember.run.once,
run: Ember.run,
schedule: Ember.run.schedule,
scheduleOnce: Ember.run.scheduleOnce,
throttle: Ember.run.throttle,
},
"@ember/service": {
default: Ember.Service,
inject: Ember.inject.service,
},
"@ember/string": {
w: Ember.String.w,
dasherize: Ember.String.dasherize,
decamelize: Ember.String.decamelize,
camelize: Ember.String.camelize,
classify: Ember.String.classify,
underscore: Ember.String.underscore,
capitalize: Ember.String.capitalize,
},
"@ember/template": {
htmlSafe: Ember.String.htmlSafe,
},
"@ember/utils": {
isBlank: Ember.isBlank,
isEmpty: Ember.isEmpty,
isNone: Ember.isNone,
isPresent: Ember.isPresent,
},
jquery: { default: $ },
rsvp: {
asap: Ember.RSVP.asap,
all: Ember.RSVP.all,
allSettled: Ember.RSVP.allSettled,
race: Ember.RSVP.race,
hash: Ember.RSVP.hash,
hashSettled: Ember.RSVP.hashSettled,
rethrow: Ember.RSVP.rethrow,
defer: Ember.RSVP.defer,
denodeify: Ember.RSVP.denodeify,
resolve: Ember.RSVP.resolve,
reject: Ember.RSVP.reject,
map: Ember.RSVP.map,
filter: Ember.RSVP.filter,
default: Ember.RSVP,
Promise: Ember.RSVP.Promise,
EventTarget: Ember.RSVP.EventTarget,
},
};
Object.keys(EMBER_MODULES).forEach((mod) => {
define(mod, () => EMBER_MODULES[mod]);
});
// TODO: Remove this and have resolver find the templates
const prefix = "discourse/templates/";
@ -166,15 +15,6 @@
}
});
define("I18n", ["exports"], function (exports) {
return I18n;
});
define("htmlbars-inline-precompile", ["exports"], function (exports) {
exports.default = function tag(strings) {
return Ember.Handlebars.compile(strings[0]);
};
});
window.__widget_helpers = require("discourse-widget-hbs/helpers").default;
// TODO: Eliminate this global

View File

@ -0,0 +1,9 @@
define("I18n", ["exports"], function (exports) {
return I18n;
});
define("htmlbars-inline-precompile", ["exports"], function (exports) {
exports.default = function tag(strings) {
return Ember.Handlebars.compile(strings[0]);
};
});