Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEV - Stamp javascript files #10575

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/assets/javascripts/admin/components/ace-editor.js
Expand Up @@ -81,7 +81,7 @@ export default Component.extend({

didInsertElement() {
this._super(...arguments);
loadScript("/javascripts/ace/ace.js?v=1.4.12").then(() => {
loadScript("/javascripts/ace/ace.js").then(() => {
window.ace.require(["ace/ace"], loadedAce => {
loadedAce.config.set("loadWorkerFromBlob", false);
loadedAce.config.set("workerPath", getURL("/javascripts/ace")); // Do not use CDN for workers
Expand Down
31 changes: 25 additions & 6 deletions app/assets/javascripts/discourse/app/lib/load-script.js
@@ -1,6 +1,7 @@
import { default as getURL, getURLWithCDN } from "discourse-common/lib/get-url";
import { run } from "@ember/runloop";
import { ajax } from "discourse/lib/ajax";
import { PUBLIC_JS_VERSIONS } from "discourse/lib/public_js_versions";
import { Promise } from "rsvp";

const _loaded = {};
Expand All @@ -16,7 +17,7 @@ function loadWithTag(path, cb) {
Ember.Test.registerWaiter(() => finished);
}

s.onload = s.onreadystatechange = function(_, abort) {
s.onload = s.onreadystatechange = function (_, abort) {
finished = true;
if (
abort ||
Expand Down Expand Up @@ -50,6 +51,10 @@ export default function loadScript(url, opts) {
return Promise.resolve();
}

if (PUBLIC_JS_VERSIONS && !opts.css) {
url = cacheBuster(url);
}

// Scripts should always load from CDN
// CSS is type text, to accept it from a CDN we would need to handle CORS
const fullUrl = opts.css ? getURL(url) : getURLWithCDN(url);
Expand All @@ -62,7 +67,7 @@ export default function loadScript(url, opts) {
}
});

return new Promise(function(resolve) {
return new Promise(function (resolve) {
// If we already loaded this url
if (_loaded[fullUrl]) {
return resolve();
Expand All @@ -72,15 +77,15 @@ export default function loadScript(url, opts) {
}

let done;
_loading[fullUrl] = new Promise(function(_done) {
_loading[fullUrl] = new Promise(function (_done) {
done = _done;
});

_loading[fullUrl].then(function() {
_loading[fullUrl].then(function () {
delete _loading[fullUrl];
});

const cb = function(data) {
const cb = function (data) {
if (opts && opts.css) {
$("head").append("<style>" + data + "</style>");
}
Expand All @@ -94,11 +99,25 @@ export default function loadScript(url, opts) {
ajax({
url: fullUrl,
dataType: "text",
cache: true
cache: true,
}).then(cb);
} else {
// Always load JavaScript with script tag to avoid Content Security Policy inline violations
loadWithTag(fullUrl, cb);
}
});
}

export function cacheBuster(url) {
if (PUBLIC_JS_VERSIONS) {
const pathParts = url.split("/");
if (pathParts[1] === "javascripts") {
const version = PUBLIC_JS_VERSIONS[pathParts[2]];
if (version !== undefined) {
return `${url}?v=${version}`;
}
}
}

return url;
}
12 changes: 12 additions & 0 deletions app/assets/javascripts/discourse/app/lib/public_js_versions.js
@@ -0,0 +1,12 @@
// DO NOT EDIT THIS FILE!!!
// Update it by running `rake javascript:update`

export const PUBLIC_JS_VERSIONS = {
ace: "1.4.12",
"Chart.min.js": "2.9.3",
"chartjs-plugin-datalabels.min.js": "0.7.0",
"jquery.magnific-popup.min.js": "1.1.0",
"pikaday.js": "1.8.0",
"spectrum.js": "1.8.0",
workbox: "4.3.1",
};
Expand Up @@ -6,7 +6,7 @@ import {
setEnvironment,
isTesting,
isProduction,
isDevelopment
isDevelopment,
} from "discourse-common/config/environment";
import { setupURL, setupS3CDN } from "discourse-common/lib/get-url";
import deprecated from "discourse-common/lib/deprecated";
Expand All @@ -32,7 +32,7 @@ export default {
if (preloadedDataElement) {
const preloaded = JSON.parse(preloadedDataElement.dataset.preloaded);

Object.keys(preloaded).forEach(function(key) {
Object.keys(preloaded).forEach(function (key) {
PreloadStore.store(key, JSON.parse(preloaded[key]));

if (setupData.debugPreloadedAppData === "true") {
Expand All @@ -48,20 +48,20 @@ export default {
get() {
deprecated(`use "get-url" helpers instead of Discourse.BaseUrl`, {
since: "2.5",
dropFrom: "2.6"
dropFrom: "2.6",
});
return baseUrl;
}
},
});
let baseUri = setupData.baseUri;
Object.defineProperty(app, "BaseUri", {
get() {
deprecated(`use "get-url" helpers instead of Discourse.BaseUri`, {
since: "2.5",
dropFrom: "2.6"
dropFrom: "2.6",
});
return baseUri;
}
},
});
setupURL(setupData.cdn, baseUrl, setupData.baseUri);
setEnvironment(setupData.environment);
Expand Down Expand Up @@ -102,7 +102,7 @@ export default {
setupS3CDN(setupData.s3BaseUrl, setupData.s3Cdn);
}

RSVP.configure("onerror", function(e) {
RSVP.configure("onerror", function (e) {
// Ignore TransitionAborted exceptions that bubble up
if (e && e.message === "TransitionAborted") {
return;
Expand All @@ -125,5 +125,5 @@ export default {

window.onerror(e && e.message, null, null, null, e);
});
}
},
};
27 changes: 21 additions & 6 deletions lib/tasks/javascript.rake
Expand Up @@ -16,10 +16,10 @@ def library_src
"#{Rails.root}/node_modules"
end

def write_template(path, template)
def write_template(path, task_name, template)
header = <<~HEADER
// DO NOT EDIT THIS FILE!!!
// Update it by running `rake javascript:update_constants`
// Update it by running `rake javascript:#{task_name}`
HEADER

basename = File.basename(path)
Expand All @@ -32,13 +32,15 @@ def write_template(path, template)
end

task 'javascript:update_constants' => :environment do
write_template("discourse/app/lib/constants.js", <<~JS)
task_name = 'update_constants'

write_template("discourse/app/lib/constants.js", task_name, <<~JS)
export const SEARCH_PRIORITIES = #{Searchable::PRIORITIES.to_json};

export const SEARCH_PHRASE_REGEXP = '#{Search::PHRASE_MATCH_REGEXP_PATTERN}';
JS

write_template("pretty-text/addon/emoji/data.js", <<~JS)
write_template("pretty-text/addon/emoji/data.js", task_name, <<~JS)
export const emojis = #{Emoji.standard.map(&:name).flatten.inspect};
export const tonableEmojis = #{Emoji.tonable_emojis.flatten.inspect};
export const aliases = #{Emoji.aliases.inspect.gsub("=>", ":")};
Expand All @@ -47,7 +49,7 @@ task 'javascript:update_constants' => :environment do
export const replacements = #{Emoji.unicode_replacements_json};
JS

write_template("pretty-text/addon/emoji/version.js", <<~JS)
write_template("pretty-text/addon/emoji/version.js", task_name, <<~JS)
export const IMAGE_VERSION = "#{Emoji::EMOJI_VERSION}";
JS
end
Expand Down Expand Up @@ -84,7 +86,8 @@ task 'javascript:update' do
public: true
}, {
source: 'spectrum-colorpicker/spectrum.css',
public: true
public: true,
skip_versioning: true
}, {
source: 'favcount/favcount.js'
}, {
Expand Down Expand Up @@ -173,6 +176,7 @@ task 'javascript:update' do

]

versions = {}
start = Time.now

dependencies.each do |f|
Expand Down Expand Up @@ -206,6 +210,12 @@ task 'javascript:update' do
if f[:public_root]
dest = "#{public_root}/#{filename}"
elsif f[:public]
unless f[:skip_versioning]
package_name = f[:source].split('/').first
package_version = JSON.parse(File.read("#{library_src}/#{package_name}/package.json"))["version"]
versions[filename] = package_version
end

dest = "#{public_js}/#{filename}"
else
dest = "#{vendor_js}/#{filename}"
Expand All @@ -214,6 +224,7 @@ task 'javascript:update' do
if src.include? "ace.js"
ace_root = "#{library_src}/ace-builds/src-min-noconflict/"
addtl_files = [ "ext-searchbox", "mode-html", "mode-scss", "mode-sql", "theme-chrome", "worker-html"]
FileUtils.mkdir(dest) unless File.directory?(dest)
addtl_files.each do |file|
FileUtils.cp_r("#{ace_root}#{file}.js", dest)
end
Expand All @@ -236,5 +247,9 @@ task 'javascript:update' do
end
end

write_template("discourse/app/lib/public_js_versions.js", "update", <<~JS)
export const PUBLIC_JS_VERSIONS = #{versions.to_json};
JS

STDERR.puts "Completed copying dependencies: #{(Time.now - start).round(2)} secs"
end
27 changes: 25 additions & 2 deletions test/javascripts/lib/load-script-test.js
@@ -1,10 +1,11 @@
import loadScript from "discourse/lib/load-script";
import { loadScript, cacheBuster } from "discourse/lib/load-script";
import { PUBLIC_JS_VERSIONS as jsVersions } from "discourse/lib/public_js_versions";

QUnit.module("lib:load-script");

QUnit.skip(
"load with a script tag, and callbacks are only executed after script is loaded",
async assert => {
async (assert) => {
assert.ok(
typeof window.ace === "undefined",
"ensures ace is not previously loaded"
Expand All @@ -19,3 +20,25 @@ QUnit.skip(
);
}
);

QUnit.test("works when a value is not present", async (assert) => {
assert.equal(
cacheBuster("/javascripts/my-script.js"),
"/javascripts/my-script.js"
);
assert.equal(
cacheBuster("/javascripts/my-project/script.js"),
"/javascripts/my-project/script.js"
);
});

QUnit.test("generates URLs with a hash", async (assert) => {
assert.equal(
cacheBuster("/javascripts/pikaday.js"),
`/javascripts/pikaday.js?v=${jsVersions["pikaday.js"]}`
);
assert.equal(
cacheBuster("/javascripts/ace/ace.js"),
`/javascripts/ace/ace.js?v=${jsVersions["ace"]}`
);
});