JavaScript SyntaxError: The requested module ‘./notes.mjs’ does not provide an export named ‘default’から考える適切なimport/exportの使い方


背景

まずは該当するjavascriptコードを確認しよう。自作jsモジュール notes.mjsからいくつかの関数をインポートしたかった。

import chalk from "chalk";
import yargs from "yargs";
import notes from "./notes.mjs"
yargs(process.argv.slice(2))
    .usage('Usage: $0 <command> [options]')
    .command('add', 'add note', 
    function (yargs) {
        return yargs
        .option('title', {
            alias: 't',
            default: '',
            demandOption: true})
        .option('body', {
            alias: 'b',
            default: '',
            demandOption: true})
        }, 
    function(argv){
            notes.addNotes(argv.title, argv.body)
            // console.log(`Title: ${argv.title} Body: ${argv.title}`)
        }
    )
    .example('$0 add --title hoge', 'add title "hoge"')
    .help('h')
    .alias('h', 'help')
    .argv;

問題

しかしながら先ほどのjavascriptコードを実行しようとすると以下エラーが発生。

node app.mjs add --title hoge --body huga
file:///Users/user/node-js-basics/notes-app/app.mjs:3
import notes from "./notes.mjs"
       ^^^^^
SyntaxError: The requested module './notes.mjs' does not provide an export named 'default'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:127:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:193:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:341:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12)
Node.js v17.6.0

正直このシンタックスエラーの意味がよくわからなかった。「default」ってなんぞや・・・。

原因分析

自作javascriptモジュールがこちら。

import * as fs from 'fs'
import { add } from './utils.mjs'
const getNotes = function() {
    return 'Your notes...'
}
export const addNotes = function(title, body) {
    const notes = loadNotes()
    console.log(notes)
}
const loadNotes = function() {
    try {
        const dataBuffer = fs.readFileSync('notes.json')
        const dataJSON = JSON.toString(dataBuffer)
        return JSON.parse(dataJSON)
    } catch(e) {
        return []
    }
}

そこでjavascriptといえば我らがMDNドキュメントを見ると目から鱗。javascriptにおけるimportの構文を全く理解できていなかったことが発覚。

import defaultExport from "module-name";
import * as name from "module-name";

先述のjavascriptコードは上記の1行目と同じ表現になるがこちらは以下のように説明されている。

defaultExport:モジュールからのデフォルトのエクスポートを参照する名前。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import

つまりexport defaultで宣言したもののみがこの構文でimportできると理解(知らんけど)。

続いて2行目については以下のように説明されている。

name: インポートを参照するとき名前空間のように用いられるモジュールオブジェクトの名前。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import

つまりjavascriptでは任意のモジュールに含まれる要素を任意のオブジェクト名でimportするときはこの表現を使え、と理解(知らんけど)。

解決方法

冒頭のimprot文の表記をちょこっと変えただけ。

import chalk from "chalk";
import yargs from "yargs";
import * as notes from "./notes.mjs"

参考リンク

export – JavaScript | MDN