From 569fa8a13552e4a463bddff4aed6211c920207fc Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 3 Feb 2022 17:36:32 +0000 Subject: [PATCH] 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. --- .../javascripts/discourse/ember-cli-build.js | 5 + .../discourse/lib/rfc176-shims/.npmrc | 1 + .../discourse/lib/rfc176-shims/index.js | 57 +++++++ .../discourse/lib/rfc176-shims/package.json | 6 + app/assets/javascripts/discourse/package.json | 4 +- .../public/assets/scripts/discourse-boot.js | 160 ------------------ .../public/assets/scripts/module-shims.js | 9 + 7 files changed, 81 insertions(+), 161 deletions(-) create mode 100644 app/assets/javascripts/discourse/lib/rfc176-shims/.npmrc create mode 100644 app/assets/javascripts/discourse/lib/rfc176-shims/index.js create mode 100644 app/assets/javascripts/discourse/lib/rfc176-shims/package.json create mode 100644 app/assets/javascripts/discourse/public/assets/scripts/module-shims.js diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index 64c4055808..e6a01988ad 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -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), diff --git a/app/assets/javascripts/discourse/lib/rfc176-shims/.npmrc b/app/assets/javascripts/discourse/lib/rfc176-shims/.npmrc new file mode 100644 index 0000000000..c42da845b4 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/rfc176-shims/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/app/assets/javascripts/discourse/lib/rfc176-shims/index.js b/app/assets/javascripts/discourse/lib/rfc176-shims/index.js new file mode 100644 index 0000000000..df26adac62 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/rfc176-shims/index.js @@ -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; + }, +}; diff --git a/app/assets/javascripts/discourse/lib/rfc176-shims/package.json b/app/assets/javascripts/discourse/lib/rfc176-shims/package.json new file mode 100644 index 0000000000..60bf829f39 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/rfc176-shims/package.json @@ -0,0 +1,6 @@ +{ + "name": "rfc176-shims", + "keywords": [ + "ember-addon" + ] +} diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 056a33e088..7b234f6291 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -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": { diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js index a1fe1ebd48..1bc1786c8d 100644 --- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js +++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js @@ -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 diff --git a/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js b/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js new file mode 100644 index 0000000000..cab696c012 --- /dev/null +++ b/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js @@ -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]); + }; +});