LoginSignup
48

More than 5 years have passed since last update.

app/assets/javascripts以下のJSを全てcommonjsのrequireに書き換える

Last updated at Posted at 2015-12-24

仕事でgulpを使って僕がよくいじる一部のjsを高速でビルドできるようにしてみたのだが、JSをいじらない人の手元でなにかと差分がおかしくなったり、ビルドするタイミングを伝えないことで問題起きたり、その為に呼ばれて治すのがとにかくめんどくさかったため、思い切ってすべてのビルド工程をbrowserify_railsとbrowserify-incrementalと(そのtransform)に押し込んで、gulpを排除してみた。

そんで、browserify_rails は導入しただけだと嬉しさがあまりないので、スクリプトを書いて一気にcommonjsに置き換えることにした。

browserify_rails に関してはhokacchaさんの記事が便利。

モダンJavaScript開発環境 on Rails - クックパッド開発者ブログ

脇道にそれるが、僕は最初、これをbrowserifyを模した別の実装だと思ってスルーしていた。使ってみたら勘違いで、railsプロセスがbrowserify-incrementalを掴んで実行する実装で、それなりに便利だった

下準備

コミットログを見る限りこういうことをやったっぽい。

  • js系のgem を除去(backbone-railsとか)
  • browserify_rails の適用
    • babel6 環境の構築
    • browserify オプションを整える(既存のコードをparseするための coffeeifyとか)
  • top level this の置き換え(this => window)
  • top level var の置き換え(var hoge => window.hoge = ...)
  • sprockets require はコードより後にないといけないので、並び替える
  • SprocketsのJSTをとりあえず require_tree ./t してしまう(本当は削除したい)

注意: 元々npm環境はあったので色々はしょってると思う

スクリプトを流す

babelで書いてた。browserify-rails のセットアップしていたら .babelrc はあると思うので割愛。preset-es2015があればよいはず。

// $ npm install glob
// $ npm install babel-cli -g
// # Move to your Rails root and put this script
// $ babel-node sprockets2commonjs.js

let glob = require('glob')
let fs = require('fs')
let path = require('path')

const ROOT = "app/assets/javascripts"
const coffeeExpr = /#= require (.*)/g;
const jsExpr = /\/\/= require (.*)/g;
const coffeeCommentOut = "#";
const jsCommentOut = "//";

/* Ignore rules */
const whitelist = [
  "i18n"
];

/* Just comment out */
const commentOutList = [
  /^t\//,
  /^i18n\//
];

function convertSprocketPathToCommonjsPath(root, fpath, spath) {
  if (/^\./.test(spath)) {
    return spath;
  }
  let relToRoot = path.relative(fpath, root);
  let rel = path.join(relToRoot, spath).replace(/^(\.\.\/)/, "");
  return rel.indexOf("..") > -1 ? rel : "./" + rel;
}

function listIncludesPath (list, p) {
  for (let w of list) {
    if (
      (typeof w === "string") &&
      w === p
    ) {
      return true;
    } else if (
      (w instanceof RegExp) &&
      w.test(p)
    ) {
      return true;
    } else {
      continue
    }
  }
  return false
}

glob.sync(`${ROOT}/**/*`, {nodir: true}).forEach(f => {
  let c = fs.readFileSync(f).toString();
  const expr = (path.extname(f) === ".coffee") ? coffeeExpr : jsExpr
  const commentOut = (path.extname(f) === ".coffee") ? coffeeCommentOut : jsCommentOut
  let ret = c.replace(expr, (_, p) => {
    if (listIncludesPath(whitelist, p)) {
      return `${commentOut}= require ${p}`
    }
    if (listIncludesPath(commentOutList, p)) {
      return `${commentOut} require('${p}');`
    }
    return `require("${convertSprocketPathToCommonjsPath(ROOT, f, p)}");`
  });
  if (ret != c) {
    fs.writeFileSync(f, ret)
  }
});

非常に雑なので参考までに。このスクリプトは全ての require を 相対パスに変換しながら commonjs に置き換える。coffeeとjsに対応。(今回はこれだけしか要らなかったので)。発想はシンプルなので、ぶっちゃけrubyなりで書いた方がいいと思う。

実際には、このスクリプトで問題の全てを解決はしないと思う。流したあとに微調整が必要になるだろう。適当にスクリプトを書き換えるなり、返還後のものを微調整する必要がある。

感想戦

最初は手作業でやっていたのだが、書き換えてる最中にミスが頻発したり、書いてる間にどんどんコンフリクトしていったので、諦めて変換スクリプトを書いたが、それが正解だったと思う。下拵え => 変換スクリプトでコンフリクト耐性があった。

とはいえ、もともと全てのjsを1つに圧縮してしまっていたため、比較的楽だったとは思う。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48