🌈

JavaScript や CSS で ~/ から始まるパスをマッピングして import する方法 2020

2021/01/01に公開約13,000字

この記事では ~/ を任意のディレクトリにマッピングするための方法を解説します。

TL;DR

import util from '~/util'; のように ~/ から始まるパスにしたとき、 src ディレクトリからの相対パスとしてマッピングするには、2020 年 12 月現在、次の方法があります。

  • Parcel もしくは Nuxt.js を使う場合、設定しなくても自動でマッピングされます
  • webpack を使う場合、webpack.config.jsresolve.alias を設定します
  • Babel だけを使う場合、 babel-plugin-module-resolver を使って設定します
  • tsc で型チェックする場合、もしくは Next.js を使う場合、tsconfig.jsonpaths を設定します
「~/ を任意のディレクトリにマッピングする」メリットとは

例えば、ディレクトリ階層が深いファイルから階層が浅いファイルを読み込む場合を考えてみます。
次のように、ディレクトリ階層が浅いファイルを読み込むには、相対パスで ../ を多く書く必要があります
このとき、「対象のファイルがどこにあるのかわかりづらい」「違うディレクトリにあるファイルへのパスが長くなる」などの問題があります。

import HeroImage from '../HeroImage';
import util from '../../../../util';

問題を解決するために、任意のディレクトリを ~/ にマッピングすると、次のように書くことができます。
この記法の良い点は、「対象のファイルを探しやすい」「パスの表記がわかりやすくなる」などが挙げられます。
また、ディレクトリ階層は、対象のファイルの関心を表すことも多いので、「対象のファイルの関心が可視化されやすい」という利点もあります。

import HeroImage from '~/domains/home/components/HeroImage';
import util from '~/util';

VSCode

VSCode では、tsconfig.json もしくは jsconfig.json の設定によって、パスをマッピングできます。
また、設定の javascript.preferences.importModuleSpecifiernon-relative に設定すると、auto imports 時のパスを ~/ から始まるパスにできます。

Reference

javascript.preferences.importModuleSpecifier
Preferred path style for auto imports.

  • shortest: Prefers a non-relative import only if one is available that has fewer path segments than a relative import.
  • relative: Prefers a relative path to the imported file location.
  • non-relative: Prefers a non-relative import based on the baseUrl or paths configured in your jsconfig.json / tsconfig.json.
  • project-relative: Prefers a non-relative import only if the relative import path would leave the package or project directory. Requires using TypeScript 4.2+ in the workspace.

https://code.visualstudio.com/docs/getstarted/settings

Atom

Atom で JavaScript を書く場合は、autocomplete-modules を使うと、パスの auto complete ができます。
autocomplete-modules では、"Webpack support" を有効にすることで、webpack.config.jsresolve.alias の設定からパスをマッピングできます。

また、"Babel Plugin Module Resolver support" を有効にすることで、babel-plugin-module-resolver の設定を読み込んでパスをマッピングできます。

Atom で TypeScript を書く場合は、atom-typescript もしくは ide-typescript によってtsconfig.json を使ってパスの auto complete ができます。

WebStorm / IntelliJ

WebStorm / IntelliJ では、webpack.config.jsresolve.alias の設定によって、パスをマッピングできます。
プロジェクトルートディレクトリに webpack.config.js がない場合、Preferences > Languages & Frameworks > JavaScript > Webpackwebpack.config.js へのパスを設定すると読み込まれます。

Reference

WebStorm runs webpack under the hood when you open a project or change webpack.config.js and, thanks to the information it gets, WebStorm now properly understands the project resolve roots and resolve aliases.
By default WebStorm will analyze the webpack configuration file in the root of the project, but you can select another file in Preferences | Languages & Frameworks | JavaScript | Webpack.
https://blog.jetbrains.com/webstorm/2017/06/webstorm-2017-2-eap-172-2827/

また、TypeScript を書く場合は、tsconfig.json を使ってパスをマッピングできます。
Preferences/Settings > Editor > Code Style > TypeScript から Use paths relative to tsconfig.json を有効にすると、tsconfig.json が読み込まれます。

Reference

For TypeScript, you can use the paths that are relative to the nearest tsconfig.json file with the option “Use paths relative to tsconfig.json”, which is in the Imports tab in Preferences/Settings | Editor | Code Style | TypeScript.
https://blog.jetbrains.com/webstorm/2020/07/configuring-the-style-of-imports-in-javascript-and-typescript/

Parcel

Parcel v1 では、~/ をマッピングする機能が有効になっているため、追加の設定は必要ありません
~/ は、主に「entrypoint となるファイルがあるディレクトリ」としてマッピングされます。

Reference

~/foo resolves foo relative to the nearest package root or, if not found, the entry root.
https://parceljs.org/module_resolution.html#~-tilde-paths

Parcel v2 では挙動が変わり、主に「package.json があるディレクトリ」か「プロジェクトのルートディレクトリ」のどちらか、呼び出し元のファイルから近いディレクトリとしてマッピングされます。

Reference

~/foo resolves foo relative to nearest node_modules directory, the nearest directory with package.json or the project root - whichever comes first.
https://v2.parceljs.org/features/module-resolution/#tilde-paths

Next.js

Next.js は、 tsconfig.json もしくは jsconfig.json の設定から、パスをマッピングできます。

Reference

Next.js automatically supports the tsconfig.json and jsconfig.json "paths" and "baseUrl" options since Next.js 9.4.
https://nextjs.org/docs/advanced-features/module-path-aliases

Nuxt.js

Nuxt.js では、~/ をマッピングする機能が有効になっているため、追加の設定は必要ありません
~/ は、nuxt.config.jssrcDir としてパスがマッピングされます

Reference

By default the source directory (srcDir) and the root directory (rootDir) are the same. You can use the alias of ~ for the source directory.
You can use the alias of ~~ or @@ for the root directory.
We recommend using the ~ as an alias. @ is still supported but will not work in all cases such as with background images in your css.
https://nuxtjs.org/docs/2.x/directory-structure/assets/#aliases

webpack

webpack では、webpack.config.jsresolve.alias を使って、パスをマッピングできます。

webpack.config.js
const path = require('path');

module.exports = {
  resolve: {
    alias: {
      '~': path.resolve(__dirname, 'src'),
    },
  },
};

Rollup

Rollup では、@rollup/plugin-alias を使って、パスをマッピングできます。

rollup.config.js
const path = require('path');
const alias = require('@rollup/plugin-alias');

module.exports = {
  plugins: [
    alias({
      entries: {
        '~': path.resolve(__dirname, 'src'),
      },
    }),
  ],
};

TypeScript

TypeScript では、 tsconfig.json にある paths フィールドによって、パスをマッピングできます。
型チェックのときにもマッピングされるため、TypeScript を書くときには、他ツールでのマッピングと同じになるように tsconfig.json を設定する必要があります

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "~/*": ["src/*"]
    }
  }
}

https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping

Babel

Babel 単体でトランスパイルするとき、babel-plugin-module-resolver を使うとパスをマッピングできます。

babel.config.js
module.exports = {
  plugins: [
    [
      'module-resolver',
      {
        alias: {
          '~': './src',
        },
      },
    ],
  ],
};

PostCSS

PostCSS 単体でトランスパイルするとき、postcss-importpostcss-import-resolver を組み合わせることで、パスをマッピングできます。

postcss.config.js
const path = require('path');
const resolver = require('postcss-import-resolver');

module.exports = {
  plugins: {
    'postcss-import': {
      resolve: resolver({
        alias: {
          '~': path.resolve(__dirname, 'src'),
        },
      }),
    },
  },
};

ESLint

eslint-plugin-import でパスを解決するとき、任意の resolver を設定することでパスをマッピングできます
webpack.config.jsresolve.alias からパスをマッピングする場合は、eslint-import-resolver-webpack をインストールして、次のように設定します。

.eslintrc.js
module.exports = {
  extends: ['plugin:import/recommended'],
  plugins: ['import'],
  settings: {
    'import/resolver': {
      webpack: {
        config: './webpack.config.js',
      },
    },
  },
};

tsconfig.jsonpaths からパスをマッピングする場合は、@typescript-eslint/parsereslint-import-resolver-typescript をインストールして、次のように設定します。

.eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  extends: ['plugin:import/recommended', 'plugin:import/typescript'],
  plugins: ['@typescript-eslint', 'import'],
  settings: {
    'import/resolver': {
      typescript: {
        project: './tsconfig.json',
      },
    },
  },
};

Node.js (runtime)

Node.js の実行時に、パスをマッピングする方法は、主に 3 種類あります。

require option

tsconfig-paths を事前に require することで、tsconfig.json の設定からパスを実行時にマッピングできます。

node -r tsconfig-paths/register src/index.js

experimental loader option

Node.js v9.0.0 から、実験的機能として、モジュールの解決に外部の loader を使えます
@node-loader/import-maps は、loader 機能によって、import maps の設定からパスをマッピングできます。

import-map.json
{
  "imports": {
    "~/": "./src/"
  }
}
IMPORT_MAP_PATH=import-map.json node --experimental-loader @node-loader/import-maps src/index.mjs

imports field

Node.js v14.6.0 から、 package.jsonimports field を使って、実行時にパスをマッピングできます。
ただし、この機能では # から始まるパスしか設定できないため、ここの例では #~/ から始まるパスをソースディレクトリにマッピングしています。

Reference

Entries in the imports field must always start with # to ensure they are disambiguated from package specifiers.
https://nodejs.org/dist/v15.5.0/docs/api/packages.html#packages_subpath_imports

All instances of * on the right hand side will then be replaced with this value, including if it contains any / separators.
https://nodejs.org/dist/v15.5.0/docs/api/packages.html#packages_subpath_patterns

package.json
{
  "type": "module",
  "imports": {
    "#~/*": "./src/*.mjs"
  }
}

Deno

Deno では、実験的機能として import maps によるパスのマッピングに対応しています。

Reference

This is an unstable feature.
Deno supports import maps.
You can use import maps with the --import-map=<FILE> CLI flag.
https://deno.land/manual@v1.6.3/linking_to_external_code/import_maps

import-map.json
{
  "imports": {
    "~/": "./src/"
  }
}
deno run --unstable --import-map=import-map.json src/index.ts

Browser

2020 年 12 月現在、Chromium では試験的機能として、import maps を使ってパスをマッピングできます

caniuse.com

Caniuse (import maps)

https://caniuse.com/import-maps (2021 年 1 月 1 日参照)

<script type="importmap">
  {
    "imports": {
      "~/": "/scripts/"
    }
  }
</script>
<script src="/scripts/index.mjs" type="module"></script>

Discussion

ログインするとコメントできます