2020/01/17 - [엥휴/Electron-Webpack + React] - 단 한 줄! index.js로 내 코드 깔끔하게 import하기에서 소개한대로, export default
를 통해 각 파일에서 클래스를 내보낸 뒤, index.js
를 통해 import 하다보니, 두 개 이상의 클래스를 export 해야하는 상황이 찾아왔다.
이때 export default { foo, bar }
처럼 클래스를 JSON으로 만들어 처리했다. index.js
는 아래와 같이 변형했다.
const glob = require('glob')
const path = require('path')
const folder = './src/components/'
const select = '!(index)'
const format = '.js'
const local = folder + select + format
module.exports = {}
glob.sync(local).forEach(file => {
const name = path.basename(file, format)
const m = require('./' + name).default
if (typeof(m) === "function") {
module.exports[m.name] = m
return
}
if (typeof(m) === "object") {
module.exports = {
...module.exports,
...m,
}
return
}
})
아래와 같은 에러가 발생한다.
Warning:
React.createElement: type is invalid -- expected a
string (for built-in components) or a
class/function (for composite components) but got:
undefined. You likely forgot to export your
component from the file it's defined in, or you
might have mixed up default and named imports.
Uncaught Error: Element type is invalid: expected a string
(for built-in components) or a class/function (for
composite components) but got: undefined. You
likely forgot to export your component from the
file it's defined in, or you might have mixed up
default and named imports.
좀더 테스트 해보니 같은 클래스를 외부 폴더에서 import 했을 때는 불러와지나, 같은 폴더 내 다른 파일에서 불러올 때는 값이 undefined가 된다.
1차 시도
분명 export default { foo, bar }
에 문제가 있다. 처음에는 default가 문제인 줄 알았다. 그래서 default를 없애고 index.js
도 다음과 같이 바꾸었다.
const glob = require('glob')
const path = require('path')
const folder = './src/components/'
const select = '!(index)'
const format = '.js'
const local = folder + select + format
module.exports = {}
glob.sync(local).forEach(file => {
const fileName = path.basename(file, format)
const m = require('./' + fileName)
if (m.default !== undefined) {
const { name } = m.default
module.exports[name] = m.default
} else {
module.exports = {
...module.exports,
...m,
}
}
})
음... 여전하다..!
2차 시도
require을 통해 가져오거나, index.js를 거치지 않고 문제가 되는 파일만 따로 import 하면 정상 사용이 가능하지만, 점점 관리가 불편해질 것 같다는 생각에 방법을 더 찾아봤다. require와 import의 차이에 대해 검색하던 중 아래와 같은 글을 발견했다.
중요한 부분은 결론
바벨과 같은 트랜스파일링 모듈을 사용한다면 주저없이 ES6 를 사용합니다. 혼용하는것도 가능하지만 가급적이면 통일되게 사용하는 것이 좋다고 생각합니다. 실제로 제가 프로젝트 진행하면서 mocha 테스트 중 ES6 의 import/export 와 CommonJs 의 module.exports 를 혼용하여 사용시 문제가 발생했었습니다(자세히 언급하지 않음).
이 의견을 보고나니 ES6의 export 자체 문제가 아니라는 생각이 들었고, webpack 문제가 아닐까 짐작했다. 빙고. 금방 비슷한 문제를 겪은 사용자를 찾을 수 있었다.
친절한 답변. default 개념을 익히는 데 도움이 되었다. 일단 아래 두 코드는 같은 표현이며, foo
값이 무엇이던 그걸 default로 설정한다는 의미이다.
export default function foo () {
return 'index foo'
}
function foo () {
return 'index foo'
}
export { foo as default };
아래 두 코드도 같은 표현이지만 의미가 살짝 다르다.
export default foo;
const _default = foo;
export { _default as default };
위 코드는 모듈이 const _default = foo;
를 수행하기 전에는 TDZ error가 발생하거나, default 값이 undefined로 설정될 수 있다. 이걸 기반으로 궁리를 해봤는데.. 역시 모르겠다.
해결
답은 의외로 간단했다. 동기적으로 실행되는 반면, default가 아닌 모듈들은 구조 분해 할당을 통해 비동기적으로 실행된다. forEach도 비동기, 구조 분해 할당도 비동기이니 개발자가 실행 시점을 예측하거나 제어하기 매우 힘들다. 고로 코드를 동기적으로 바꿔주었다.
const glob = require('glob')
const path = require('path')
const folder = `./${__dirname}/`
const select = '!(index)'
const format = '.js'
const local = folder + select + format
module.exports = {}
for (const file of glob.sync(local)) {
const fileName = path.basename(file, format)
const m = require('./' + fileName)
for (const ele of Object.values(m)) {
module.exports[ele.name] = ele
}
}
이중 for 문이 밉지만... 뭐 상관하지 않기로 했다. 겸사겸사 folder도 general 하게 바꿨다.
'React' 카테고리의 다른 글
이슈 13 electron react-router-dom (별로 특별할 것 없음), HashRouter BrowserRouter 차이 (0) | 2020.02.16 |
---|---|
이슈 12 react form 초기화하는 세 가지 방법 (0) | 2020.02.03 |
이슈 10 react 다국어 지원, import { default as smt } from ... (0) | 2020.01.29 |
이슈 9 Parsing error: Unexpected token = eslint (0) | 2020.01.22 |
이거 하나만 있으면 클린 코드 해결! ESLint (0) | 2020.01.22 |