のんびりSEの議事録

プログラミング系のポストからアプリに関してのポストなどをしていきます。まれにアニメ・マンガなど

WebpackerからShakapackerへ移行したときの備忘録

RailsWebpackerを使用していたのですが、こちらが開発終了となり、fork先にShakapackerが引き継いでいるので、そちらに移行したときの備忘録です。

Webpacker5.4 -> Shakapacker6.1 へ移行したため、途中のWebpacker5.4以上の対応も含まれてます

移行した手順

  1. Gemfileのwebpackershakapackerに書き換える(バージョンが5系の場合は6系にする)
  2. npm packageから@rails/webpackerを削除する
yarn remove @rails/webpacker
  1. shackapackerをインストールする(shackepacekrの場合は@railsの接頭詞は不要)
yarn add shackapacker
  1. 依存packegeのインストール
yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime babel-loader compression-webpack-plugin terser-webpack-plugin webpack webpack-assets-manifest webpack-cli webpack-merge webpack-sources webpack-dev-server
  1. bin/webpack -> bin/webpacker に, bin/webpack-dev-server -> bin/webpacker-dev-serverに名前を変える
$ git mv bin/webpack bin/webpacker
$ git mv bin/webpack-dev-server bin/webpacker-dev-server
  1. 設定ファイルが単一のconfig/webpack/webpack.config.jsの場合は以下のように修正する
const { env, webpackConfig } = require('shakapacker')
const { existsSync } = require('fs')
const { resolve } = require('path')

const envSpecificConfig = () => {
  const path = resolve(__dirname, `${env.nodeEnv}.js`)
  if (existsSync(path)) {
    console.log(`Loading ENV specific webpack configuration file ${path}`)
    return require(path)
  } else {
    console.log(`WARNING: Using default webpack configuration. Did not find a Env specific file at path ${path}`)
    return webpackConfig
  }
}

module.exports = envSpecificConfig()
  1. webpacker.ymlを修正する
  source_path: app/javascript
  source_entry_path: /
  1. app/javascript/packs/*以下にあるファイルをapp/javascript/に移動する
$ git mv app/javascript/packs/* app/javascript/
  1. webpacker:install タスクを実行しwebpacker関連のファイルを上書きする(mオプションで必要に応じて既存の設定をmergeする)
$ bundle exec rails webpacker:install
  1. javascript_packs_with_chunks_tagjavascript_packs_tagに、stylesheet_packs_with_chunks_tagstylesheet_pack_tagに変更する

  2. config/webpack/environment.jsconfig/webpack/base.jsに変更し、以下の修正を加える

// config/webpack/base.js
const { webpackConfig, merge } = require('@rails/webpacker');
const customConfig = {
  module: {
    rules: [
      {
        test: require.resolve('jquery'),
        loader: 'expose-loader',
        options: {
          exposes: ['$', 'jQuery']
        }
      }
    ]
  }
};

module.exports = merge(webpackConfig, customConfig);

12 . package.jsonにbbrowserslistの設定が存在する場合、.browserslistrcファイルを削除する

// package.json
  "browserslist": [
    "defaults"
  ],
  1. babel.config.jsを削除する。デフォルトでは以下の設定を読み込むようになる
  "babel": {
    "presets": [
      "./node_modules/shakapacker/package/babel/preset.js"
    ]
  },
  1. webpacker.ymlからextensionsを削除する。config/webpack/base.jsextensionsの内容をmergeする
// config/webpack/base.js
const { webpackConfig: baseWebpackConfig, merge } = require('shakapacker')

const options = {
  resolve: {
      extensions: ['.css', '.ts', '.tsx']
  }
}

// Copy the object using merge b/c the baseClientWebpackConfig is a mutable global
// If you want to use this object for client and server rendering configurations,
// havaing a new object is essential.
module.exports = merge({}, baseWebpackConfig, options)
  1. webpacker.ymlwatched_pathsがある場合、additional_pathsに書き換える

  2. 以下のコミットで消されたパッケージがある場合、削除する

https://github.com/rails/webpacker/pull/3056/files

$ yarn remove babel-plugin-macros case-sensitive-paths-webpack-plugin core-js regenerator-runtime
  1. bin/webpackを実行しエラーが出ないことを確認する
./bin/webpack
  1. railsタスクからcompileが通ることを確認する
RAILS_ENV=production bin/rails assets:precompile
bin/rails assets:clobber // assetsをclean
  1. webpack-dev-serverが動くことを確認する
yarn add webpack-dev-server 
  1. 他のscriptで/bin/webpack, bin/webpack-dev-serverを記述している箇所があれば、bin/webpacker, bin/webpacker-dev-serverに修正する

typescriptの場合

  • tsconfig.jsonを以下のように修正
{
  "compilerOptions": {
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es6", "dom"],
    "module": "es6",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "*": ["node_modules/*", "app/packs/*"]
    },
    "sourceMap": true,
    "target": "es5",
    "noEmit": true
  },
  "exclude": ["**/*.spec.ts", "node_modules", "vendor", "public"],
  "compileOnSave": false
}
  • 依存パッケージのインストール
$ yarn add typescript @babel/preset-typescript
  • webpack.config.js(環境別であればconfig/webpack/base.js)を修正する
const { webpackConfig, merge } = require("shakapacker");
const ForkTSCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");

module.exports = merge(webpackConfig, {
  plugins: [new ForkTSCheckerWebpackPlugin()],
});
  • ${RAILE_ENV}.jsでtoWebpackConfigは不要

babel.config.jsをカスタマイズする

https://github.com/shakacode/shakapacker/blob/208e4558dd7f356be0116ea47915fb2b96e500be/docs/customizing_babel_config.md

// babel.config.js
module.exports = function (api) {
  const defaultConfigFunc = require('shakapacker/package/babel/preset.js') // defaultで使用される設定を読み込む
  const resultConfig = defaultConfigFunc(api)
  const isDevelopmentEnv = api.env('development')
  const isProductionEnv = api.env('production')
  const isTestEnv = api.env('test')

  const changesOnDefault = {
    presets: [
      [
        '@babel/preset-react',
        {
          development: isDevelopmentEnv || isTestEnv,
          useBuiltIns: true
        } 
      ]
    ].filter(Boolean),
    plugins: [
      isProductionEnv && ['babel-plugin-transform-react-remove-prop-types', 
        { 
          removeImport: true 
        }
      ],
      process.env.WEBPACK_SERVE && 'react-refresh/babel'
    ].filter(Boolean),
  }

  // デフォルトの設定とカスタム設定をmergeする
  resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets]
  resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ]

  return resultConfig
}

css

  • 各種css loaderをインストールする
$ yarn add css-loader style-loader mini-css-extract-plugin css-minimizer-webpack-plugin
  • resolve.extensionsを修正する
// config/webpack/webpack.config.js
const { webpackConfig, merge } = require('shakapacker')
const customConfig = {
  resolve: {
    extensions: ['.css']
  }
}

module.exports = merge(webpackConfig, customConfig)

sass

$ yarn add sass sass-loader

移行時に躓いたポイント

static_assets_extensionsについて

削除されているため、設定しても無意味

  • Webpack5 からAsset Modulesが採用されており、その設定が組み込まれているため基本的には修正不要

https://github.com/shakacode/shakapacker/blob/2b70a6f67a71a607b8cf2f616bb2a5eba894723c/package/rules/file.js

Error: Can't resolve './src'

  • webpack5以降デフォルトでのentry pointはsrc/index.js
  • webpackの設定をoutputしてみるが、entryは正しく設定されていた

原因

環境別のconfig/webpack/${RAILE_ENV}.jsにてmodule.exportsをしてなかった...

  • config/webpack/development.js
process.env.NODE_ENV = process.env.NODE_ENV || 'development'

const base = require('./base')

module.exports = base // これを忘れていた...
  • config/webpack/base.js
const { webpackConfig, merge, environment } = require('shakapacker');
const ForkTSCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const path = require("path")


const customConfig = {
  resolve: {
    extensions: [
      ".tsx",
      ".ts",
      ".mjs",
      ".js",
      ".jsx",
      ".sass",
      ".scss",
      ".css",
      ".module.sass",
      ".module.scss",
      ".module.css",
      ".png",
      ".svg",
      ".gif",
      ".jpeg",
      ".jpg"
    ]
  },
  module: {
    rules: [
      {
        test: require.resolve('jquery'),
        loader: 'expose-loader',
        options: {
          exposes: ['$', 'jQuery']
        }
      }
    ]
  },
  plugins: [new ForkTSCheckerWebpackPlugin({
    typescript: {
      configFile: path.resolve(__dirname, "../../tsconfig.json"),
    },
    async: false,
  })],
};

module.exports = merge(webpackConfig, customConfig);
  • config/webpack/webpack.config.js
const { env, webpackConfig } = require('shakapacker')
const { existsSync } = require('fs')
const { resolve } = require('path')

const envSpecificConfig = () => {
  const path = resolve(__dirname, `${env.nodeEnv}.js`)
  if (existsSync(path)) {
    console.log(`Loading ENV specific webpack configuration file ${path}`)
    return require(path)
  } else {
    console.log(`WARNING: Using default webpack configuration. Did not find a Env specific file at path ${path}`)
    return webpackConfig
  }
}

module.exports = envSpecificConfig()

TypeError: Cannot read properties of undefined (reading 'get')

at WebpackAssetsManifest.handleProcessAssetsAnalyse (...node_modules/webpack-assets-manifest/src/WebpackAssetsManifest.js:467:37))

application.tsを一旦コメントアウトすると動作したため、 どこかのファイルの書き方の問題くさい(もう少しわかりやすいエラーでないのか...)

expose-loaderを入れてなかったのが原因

https://runebook.dev/ja/docs/webpack/loaders/expose-loader https://site-builder.wiki/posts/17882

webpackのProvidePluginを使ったほうが良さそう

// app/packs/entrypoints/application.js

- import jQuery from 'jquery'
- window.jQuery = jQuery
// config/webpack/webpack.config.js

const webpack = require('webpack')
const { webpackConfig, merge } = require('shakapacker')

module.exports = merge(webpackConfig, {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    })
  ],
})

require.context('./images', true)

かつてimage_pack_tag helperを使用するために使われていたもの

おそらく不要になった(webpackの設定から読み込むようになった) https://github.com/shakacode/shakapacker/blob/4d41b5a4ad3a7b7f4373c2ae0c8f2d0d697e389c/lib/webpacker/helper.rb#L169

Stimuls JS Class constructor Controller cannot be invoked without 'new'

browserlistに"not IE 11"を指定すると解決する

https://github.com/hotwired/stimulus/issues/433

imagesが読み込めない

https://github.com/rails/webpacker/issues/2956

TypeError: Cannot read properties of undefined (reading 'get') at WebpackAssetsManifest.handleProcessAssetsAnalyse (//node_modules/webpack-assets-manifest/src/WebpackAssetsManifest.js:467:37)

以下のようにruleを追加する(フォルダは他に合わせてstaticにする)

    rules: [
      {
        test: [/\.mp3$/],
        exclude: /\.(js|mjs|jsx|ts|tsx)$/,
        type: 'asset/resource',
        generator: {
          filename: 'static/[name]-[hash][ext][query]'
        }
      }
    ]
  • asset_pack_path("static/images/image.png") だと読み込めない

static/imagesディレクトリは作成されないから

参考

github.com

techracho.bpsinc.jp