mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 14:43:36 +08:00
4d62c49e20
The primary motivation is to simplify `eagerLoadRawTemplateModules` which curently introspects the module dependencies (the `imports` at runtime). This is no longer supported in Embroider as the AMD shims do not have any dependencies (since it's managed internally with webpack).
253 lines
7.4 KiB
JavaScript
253 lines
7.4 KiB
JavaScript
"use strict";
|
|
|
|
const path = require("path");
|
|
const WatchedDir = require("broccoli-source").WatchedDir;
|
|
const Funnel = require("broccoli-funnel");
|
|
const mergeTrees = require("broccoli-merge-trees");
|
|
const fs = require("fs");
|
|
const concat = require("broccoli-concat");
|
|
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
|
|
const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler");
|
|
const EmberApp = require("ember-cli/lib/broccoli/ember-app");
|
|
|
|
function fixLegacyExtensions(tree) {
|
|
return new Funnel(tree, {
|
|
getDestinationPath: function (relativePath) {
|
|
if (relativePath.endsWith(".es6")) {
|
|
return relativePath.slice(0, -4);
|
|
} else if (relativePath.includes("/templates/")) {
|
|
if (relativePath.endsWith(".raw.hbs")) {
|
|
relativePath = relativePath.replace(".raw.hbs", ".hbr");
|
|
}
|
|
|
|
if (relativePath.endsWith(".hbr")) {
|
|
return relativePath.replace("/templates/", "/raw-templates/");
|
|
}
|
|
}
|
|
|
|
return relativePath;
|
|
},
|
|
});
|
|
}
|
|
|
|
const COLOCATED_CONNECTOR_REGEX =
|
|
/^(?<prefix>.*)\/connectors\/(?<outlet>[^\/]+)\/(?<name>[^\/\.]+)\.(?<extension>.+)$/;
|
|
|
|
// Having connector templates and js in the same directory causes a clash
|
|
// when outputting es6 modules. This shim separates colocated connectors
|
|
// into separate js / template locations.
|
|
function unColocateConnectors(tree) {
|
|
return new Funnel(tree, {
|
|
getDestinationPath: function (relativePath) {
|
|
const match = relativePath.match(COLOCATED_CONNECTOR_REGEX);
|
|
if (
|
|
match &&
|
|
match.groups.extension === "hbs" &&
|
|
match.groups.prefix.split("/").pop() !== "templates"
|
|
) {
|
|
const { prefix, outlet, name } = match.groups;
|
|
return `${prefix}/templates/connectors/${outlet}/${name}.hbs`;
|
|
}
|
|
if (
|
|
match &&
|
|
match.groups.extension === "js" &&
|
|
match.groups.prefix.split("/").pop() === "templates"
|
|
) {
|
|
// Some plugins are colocating connector JS under `/templates`
|
|
const { prefix, outlet, name } = match.groups;
|
|
const newPrefix = prefix.slice(0, -"/templates".length);
|
|
return `${newPrefix}/connectors/${outlet}/${name}.js`;
|
|
}
|
|
return relativePath;
|
|
},
|
|
});
|
|
}
|
|
|
|
function namespaceModules(tree, pluginName) {
|
|
return new Funnel(tree, {
|
|
getDestinationPath: function (relativePath) {
|
|
return `discourse/plugins/${pluginName}/${relativePath}`;
|
|
},
|
|
});
|
|
}
|
|
|
|
function parsePluginName(pluginRbPath) {
|
|
const pluginRb = fs.readFileSync(pluginRbPath, "utf8");
|
|
// Match parsing logic in `lib/plugin/metadata.rb`
|
|
for (const line of pluginRb.split("\n")) {
|
|
if (line.startsWith("#")) {
|
|
const [attribute, value] = line.slice(1).split(":", 2);
|
|
if (attribute.trim() === "name") {
|
|
return value.trim();
|
|
}
|
|
}
|
|
}
|
|
throw new Error(
|
|
`Unable to parse plugin name from metadata in ${pluginRbPath}`
|
|
);
|
|
}
|
|
|
|
module.exports = {
|
|
name: require("./package").name,
|
|
|
|
pluginInfos() {
|
|
const root = path.resolve("../../../../plugins");
|
|
const pluginDirectories = fs
|
|
.readdirSync(root, { withFileTypes: true })
|
|
.filter(
|
|
(dirent) =>
|
|
(dirent.isDirectory() || dirent.isSymbolicLink()) &&
|
|
!dirent.name.startsWith(".") &&
|
|
fs.existsSync(path.resolve(root, dirent.name, "plugin.rb"))
|
|
);
|
|
|
|
return pluginDirectories.map((directory) => {
|
|
const directoryName = directory.name;
|
|
const pluginName = parsePluginName(
|
|
path.resolve(root, directoryName, "plugin.rb")
|
|
);
|
|
const jsDirectory = path.resolve(
|
|
root,
|
|
directoryName,
|
|
"assets/javascripts"
|
|
);
|
|
const adminJsDirectory = path.resolve(
|
|
root,
|
|
directoryName,
|
|
"admin/assets/javascripts"
|
|
);
|
|
const testDirectory = path.resolve(
|
|
root,
|
|
directoryName,
|
|
"test/javascripts"
|
|
);
|
|
const configDirectory = path.resolve(root, directoryName, "config");
|
|
const hasJs = fs.existsSync(jsDirectory);
|
|
const hasAdminJs = fs.existsSync(adminJsDirectory);
|
|
const hasTests = fs.existsSync(testDirectory);
|
|
const hasConfig = fs.existsSync(configDirectory);
|
|
return {
|
|
pluginName,
|
|
directoryName,
|
|
jsDirectory,
|
|
adminJsDirectory,
|
|
testDirectory,
|
|
configDirectory,
|
|
hasJs,
|
|
hasAdminJs,
|
|
hasTests,
|
|
hasConfig,
|
|
};
|
|
});
|
|
},
|
|
|
|
generatePluginsTree() {
|
|
if (!this.shouldLoadPlugins()) {
|
|
return mergeTrees([]);
|
|
}
|
|
const appTree = this._generatePluginAppTree();
|
|
const testTree = this._generatePluginTestTree();
|
|
const adminTree = this._generatePluginAdminTree();
|
|
return mergeTrees([appTree, testTree, adminTree]);
|
|
},
|
|
|
|
_generatePluginAppTree() {
|
|
const trees = this.pluginInfos()
|
|
.filter((p) => p.hasJs)
|
|
.map(({ pluginName, directoryName, jsDirectory }) =>
|
|
this._buildAppTree({
|
|
directory: jsDirectory,
|
|
pluginName,
|
|
outputFile: `assets/plugins/${directoryName}.js`,
|
|
})
|
|
);
|
|
return mergeTrees(trees);
|
|
},
|
|
|
|
_generatePluginAdminTree() {
|
|
const trees = this.pluginInfos()
|
|
.filter((p) => p.hasAdminJs)
|
|
.map(({ pluginName, directoryName, adminJsDirectory }) =>
|
|
this._buildAppTree({
|
|
directory: adminJsDirectory,
|
|
pluginName,
|
|
outputFile: `assets/plugins/${directoryName}_admin.js`,
|
|
})
|
|
);
|
|
return mergeTrees(trees);
|
|
},
|
|
|
|
_buildAppTree({ directory, pluginName, outputFile }) {
|
|
let tree = new WatchedDir(directory);
|
|
|
|
tree = fixLegacyExtensions(tree);
|
|
tree = unColocateConnectors(tree);
|
|
tree = namespaceModules(tree, pluginName);
|
|
|
|
tree = RawHandlebarsCompiler(tree);
|
|
|
|
const colocateBase = `discourse/plugins/${pluginName}`;
|
|
tree = new DiscoursePluginColocatedTemplateProcessor(
|
|
tree,
|
|
`${colocateBase}/discourse`
|
|
);
|
|
tree = new DiscoursePluginColocatedTemplateProcessor(
|
|
tree,
|
|
`${colocateBase}/admin`
|
|
);
|
|
tree = this.compileTemplates(tree);
|
|
|
|
tree = this.processedAddonJsFiles(tree);
|
|
|
|
return concat(mergeTrees([tree]), {
|
|
inputFiles: ["**/*.js"],
|
|
outputFile,
|
|
allowNone: true,
|
|
});
|
|
},
|
|
|
|
_generatePluginTestTree() {
|
|
const trees = this.pluginInfos()
|
|
.filter((p) => p.hasTests)
|
|
.map(({ pluginName, directoryName, testDirectory }) => {
|
|
let tree = new WatchedDir(testDirectory);
|
|
|
|
tree = fixLegacyExtensions(tree);
|
|
tree = namespaceModules(tree, pluginName);
|
|
tree = this.processedAddonJsFiles(tree);
|
|
|
|
return concat(mergeTrees([tree]), {
|
|
inputFiles: ["**/*.js"],
|
|
outputFile: `assets/plugins/test/${directoryName}_tests.js`,
|
|
allowNone: true,
|
|
});
|
|
});
|
|
return mergeTrees(trees);
|
|
},
|
|
|
|
shouldCompileTemplates() {
|
|
// The base Addon implementation checks for template
|
|
// files in the addon directories. We need to override that
|
|
// check so that the template compiler always runs.
|
|
return true;
|
|
},
|
|
|
|
treeFor() {
|
|
// This addon doesn't contribute any 'real' trees to the app
|
|
return;
|
|
},
|
|
|
|
// Matches logic from GlobalSetting.load_plugins? in the ruby app
|
|
shouldLoadPlugins() {
|
|
if (process.env.LOAD_PLUGINS === "1") {
|
|
return true;
|
|
} else if (process.env.LOAD_PLUGINS === "0") {
|
|
return false;
|
|
} else if (EmberApp.env() === "test") {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
};
|