Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mizchi/ca3729b062906715d76df12efdc72fea to your computer and use it in GitHub Desktop.
Save mizchi/ca3729b062906715d76df12efdc72fea to your computer and use it in GitHub Desktop.

Webpack で WebWorker + サードパーティ WebWorker

https://battle-saga-jp.connpass.com/event/142700/ の発表資料

今日の webpack.config.js はこちら

https://gist.github.com/mizchi/0af8dee5c682a0f9ef8470a089ec41b9

注意: この記事中で出るもののうち、 まだ CDN でのサードパーティモードの publish に成功してない (localhost では動いた…)

mdbuf の紹介

https://mdbuf.netlify.com https://github.com/mizchi/mdbuf

  • とにかく Markdown のプレビュー速いだけのツール
  • WebWorker による off thread compile
  • プレビューの差分レンダリング
  • コードハイライト
  • プレビュー位置の自動追従
  • prettier による format
  • 数式対応

前提

https://mizchi.hatenablog.com/entry/2018/10/02/093750

要は webworker 使っていこうぜという話

Lighthouse のスコア

メトリクス

  • Main Thread の初期化に 500ms
  • Worker Theard の初期化に 200ms

やったこと

  • 重いモジュールを WebWorker に追い出す
  • KaTeX のフォントを必要になるまで読まないようにする

重いモジュール

  • remark とそのプラグイン: 400KB
  • prettier/standalone: 1.2MB

これらを Comlink で Worker に追い出す

この辺 https://github.com/mizchi/mdbuf/blob/master/src/worker/index.ts

Comlink

WebWorker を使いやすくラップしてくれる

// worker thread
import * as comlink from "comlink"
comlink.expose({
  async foo() { return 1; }
});

// main thread
import * as comlink from "comlink"
const api = comlink.wrap(new Worker('./worker'));
const ret = await api.foo();

いい感じに Worker を作りたい

comlink は worker インスタンスを食うだけで、その生成はユーザーが頑張る

本題: Webpack でいい感じに Worker を生成する

  • importScripts 使いたくない
  • Webpack で TS+ESM をコンパイルしたい

Webpack で WebWorker で作るぞ!

worker-plugin

https://github.com/GoogleChromeLabs/worker-plugin

preact の developit 氏(@Google)

const worker = new Worker('./foo.ts', {type: 'module'});

{type: "module"} で渡されると Webpack でコンパイルされる。

将来的な仕様に沿っている(らしい)

mdbuf は主にこれ

worker-loader

https://github.com/webpack-contrib/worker-loader

      {
        test: /\.worker\.ts$/,
        use: [
          "worker-loader",
          "ts-loader"
        ]
      },
import Worker from './foo.worker.ts';
comlink.wrap(new Worker());

loader で new すると Worker インスタンスになる loader。

mdbuf のサードパーティ対応

やりたいこと: Mdbuf を WebComponents で配れるようにしたかった

<x-mdbuf-preview>
# Hello
</x-mdbuf-preview>

mdbuf では worker-loader を使ったが…

サードパーティ編

コンテキスト

「でもお前自作の mdbuf のプレビューできるのそのツールだけじゃん」

これがしたくなった

<script src="http://localhost:9999/mdbuf-preview.js" async></script>
<!-- 後でできるように -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@mizchi/mdbuf@0.1.0/dist/mdbuf-preview.js"></script>
-->

<mdbuf-preview>
  <pre>
        ---
        title: aaa
        ---

        # Markdown Buffer

        - Desktop PWA Support
        - Autosave
        - Off Thread Markdown Compiling

        ## Markdown

        **emphasis** ~~strike~~ _italic_

        > Quote

        ## Math by KaTeX

        $ y = x^3 + 2ax^2 + b $
  </pre>
</mdbuf-preview>

様々な制約

サードパーティ制約

same origin じゃないと new Worker() できない…

webpack publicPath

webpack の chunk を指定する場合、相対パスを与える必要がある

最終的にこうなった

// 抜粋
const WorkerPlugin = require("worker-plugin");

module.exports = {
  output: {
    publicPath: process.env.ASSET_HOST || "/",
    globalObject: "self",
    filename: "[name].js"
  },
  module: {
    rules: [
      {
        test: /\.w\.ts$/,
        use: [
          {
            loader: "worker-loader",
            options: {
              publicPath: process.env.ASSET_HOST || "/",
              inline: true
            }
          },
          'ts-loader'
        ]
      }
    ]
  },
  plugins: [
    new WorkerPlugin(),
  ]
};

要はインラインワーカー技法を使う

// インラインワーカーの例
const url = URL.createObjectURL(
  new Blob([ "console.log('hello from worker')" ], { type: "text/javascript" })
);
const worker = new Worker(url);

を内部的にやってくれる worker-loader のフラグが inline: true。文字列が埋め込まれるだけなのでペイロードはあるがパースコストは安い。

これをリリースするエンドポイントに合わせてビルドする(TODO: ちゃんと動かす)

ASSET_HOST="https://cdn.jsdelivr.net/npm/@mizchi/mdbuf@0.1.0/dist/" webpack --mode production

デモ

手元でなんか

本当はこれしたい

これが動くらしい

__webpack_public_path__ = process.env.ASSET_PATH;

https://webpack.js.org/guides/public-path/

なのでこれがしたい

__webpack_public_path__ = document.currentScript.url;

あと、CORS 許可が必要で netilfy じゃ動かない! 横着して jsdelivr に相乗りするぞ https://www.jsdelivr.com/package/npm/@mizchi/mdbuf?path=dist

結果

__webpack_public_path__ 動かない…(動かせた方がいたら教えて下さい)

そもそも document.currentScript で IE11 で動くポリフィルがない。

https://github.com/JamesMGreene/document.currentScript

(IE6~10 で動くポリフィル。何のために生まれてきたんだ)

Comlink の問題

Proxy Polyfill を要求する

https://github.com/GoogleChrome/proxy-polyfill

サードパーティなので Proxy を入れたくない。

なので軽量な comlink クローンを作った。

https://gist.github.com/mizchi/bf2735e6e31b5a4c66800c04d112effh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment