Defer loading JavaScript in Themes and Components

Thanks to @david we have a very clean pattern for “eager” loading JavaScript in themes.

This means you just put *.js.es6 files into the javascripts directory and it works exactly like plugins do, this is glorious.

For example this is how you do an initializer now, which has 100% parity with plugins:

  • Create a file called /javascripts/discourse/initializers/my-init.js.es6
import { withPluginApi } from "discourse/lib/plugin-api";

function initialize(api) {
  // init via api here
}

export default {
  name: "discourse-otp",

  initialize() {
    withPluginApi("0.8.28", initialize);
  }
};

This is an enormously important feature cause we can now split up large complex themes into a lot of pieces, get linting, syntax highlighting and all the good stuff.

However, in some cases we may want to optionally ship a JS payload.

For example, say we are decorating posts, but only posts with very specific markdown. There is no point downloading a 100KB fancy library till we know we are going to use it.

I worked around this in my component following this fancy change:

https://github.com/discourse/discourse/commit/719a93c312b9caa6c71de22d67f1ce1a78c1c8b2

With:

import loadScript from "discourse/lib/load-script";

function generateOtp($elem) {
  loadScript(settings.theme_uploads.jsotp).then(() => {
     // stuff goes here
  });
}

This meant

  1. I needed to add .js to theme authorized extensions
  2. I needed to add a bypass to the CSP for the particular asset in content security policy script src
  3. I needed to name the asset in my about.json

As a theme component writer, this is just too much friction cause you have no chance in the distribution game requiring this level of ninja.

I am thinking to make this usable there are 2 alternatives we can pick from

  1. We can teach the system to auto CSP the .js assets in active themes and by default allow themes to upload .js

  2. We can shift towards something like javascript_cache that non defer theme js uses.

I am kind of leaning on 1 cause adding .js to theme authorized extensions seems trivial and auto CSP should not be impossible.

@pmusaraj / @Johani / @Osama any thoughts here?

The ability to reference theme uploads in JS is a great addition! :100:

This makes a lot of sense to me because anything you can do in a .js file can already be done in files in the javascripts folder of the theme. So, I don’t see any harm in allowing themes to have .js uploads by default.

여기 남은 작업은 CSP를 설정하여 테마의 js 업로드를 허용하는 것뿐이라 이 이슈를 다시 활성화합니다. js 파일은 이미 꽤 오래 전부터 테마 업로드 대상으로 기본적으로 허용되어 있습니다.

CSP가 테마의 js 업로드를 차단하지 않는다면, Image Annotator - Allows you to annotate images in the previewer 와 같은 컴포넌트는 홈 페이지에서 의존성을 로드할 필요가 없게 됩니다(약 170kb gzip). 예를 들어 해당 컴포넌트는 컴포저가 열렸을 때만 의존성을 로드하면 됩니다. 또한 익명 방문자에게는 의존성을 로드할 필요가 전혀 없습니다.

또한 이 변경 사항은 테마가 메인 스레드에서 일부 무거운 작업을 처리할 수 있는 웹 워커 파일을 가질 수 있도록 "허용"하게 됩니다.

위에서 '허용’이라는 표현을 따옴표로 쓴 이유는, 블롭(blobs)으로 가질 수는 있지만 문자열 안에 자바스크립트를 뒤섞는 것보다는 별도의 파일로 관리하는 것이 훨씬 깔끔하기 때문입니다.

좋은 소식 하나를 전합니다. 구현이 다소 복잡하지만, 이제 테마 컴포넌트에서 웹 워커를 사용하고자 하는 경우를 위해 로컬 JS 에셋을 지원하게 됩니다.

CSP를 통해 이를 처리하는 다른 깔끔한 방법은 없으며, 워커 파일을 동일한 도메인에서 서빙하면 이 부분의 큰 취약점을 해결할 수 있습니다.

참고… 이제 출시되었습니다 :tada: