개발환경에서는 ts-loader를 사용하여 빌드하였는데, 배포 관련한 작업이 필요해 프로덕션 환경에서 babel-loader를 사용하여 빌드했더니 에러가 발생하였다. React is not defined
오류는 React 17 미만 버전에서 jsx를 해석할 때 React.createElement로 트랜스파일되기 때문에 발생하는 오류로 판단하였다. 하지만 runtime: “automatic”
을 적용하면 새로운 jsx 변환 방식을 사용하기 때문에 React import가 필요없다고 알고 있어서 이해가 되지 않았다.
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 2%, not dead",
"useBuiltIns": "usage",
"corejs": "3.37",
"shippedProposals": true
}
],
"@babel/preset-typescript",
["@babel/preset-react", { "runtime": "automatic" }],
"@emotion/babel-preset-css-prop"
],
"plugins": ["@emotion/babel-plugin"]
}
기존 바벨 테스트했을 때와 비교해보면 추가된 것은 css prop 뿐이다. 따라서 @emotion/babel-preset-css-prop
설정을 자세히 알아보았다. webpack의 문제일 수 있으므로 babel-cli를 이용하여 트랜스파일을 진행해보기로 하였다. 그래서 css prop을 사용하는 간단한 tsx 파일을 만들고 babel 명령어로 tsx 파일을 js로 만들어보았다.
$ npx babel ./src/Test.tsx --out-file ./dist/Test.js
var _react = require("@emotion/react");
: emotion에 정의된 React 모듈을 가져온다.
return (0, _react.jsx)("div", {})
: emotion React 모듈의 jsx 함수를 실행한다.
따라서 이 방식은 React를 import 하지 않으면 오류가 발생한다. "runtime": "automatic" 을 추가해서 문제가 없어야하는데 오류가 나는 것을 보면 automatic이 제대로 동작하지 않는 것을 확인할 수 있다.
const Test = () => {
return (
<div
css={{
color: 'hotpink',
'&:hover': {
color: 'darkorchid',
},
}}
>
Test
</div>
);
};
export default Test;
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 2%, not dead",
"useBuiltIns": "usage",
"corejs": "3.37",
"shippedProposals": true
}
],
"@babel/preset-typescript",
["@babel/preset-react", { "runtime": "automatic" }],
"@emotion/babel-preset-css-prop"
]
}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = require("@emotion/react");
function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
var _ref = process.env.NODE_ENV === "production" ? {
name: "1msfjgq",
styles: "color:hotpink;&:hover{color:darkorchid;}"
} : {
name: "xkig9r-Test",
styles: "color:hotpink;&:hover{color:darkorchid;};label:Test;",
map: "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9UZXN0LnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFHTSIsImZpbGUiOiIuLi9zcmMvVGVzdC50c3giLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBUZXN0ID0gKCkgPT4ge1xuICByZXR1cm4gKFxuICAgIDxkaXZcbiAgICAgIGNzcz17e1xuICAgICAgICBjb2xvcjogJ2hvdHBpbmsnLFxuICAgICAgICAnJjpob3Zlcic6IHtcbiAgICAgICAgICBjb2xvcjogJ2RhcmtvcmNoaWQnLFxuICAgICAgICB9LFxuICAgICAgfX1cbiAgICA+XG4gICAgICBUZXN0XG4gICAgPC9kaXY+XG4gICk7XG59O1xuXG5leHBvcnQgZGVmYXVsdCBUZXN0O1xuIl19 */",
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
};
const Test = () => {
return (0, _react.jsx)("div", {
css: _ref
}, "Test");
};
var _default = exports.default = Test;
@emotion/babel-preset-css-prop 은 preset/react와 preset/typescript 보다 앞에 위치해야 한다!!!
"importSource": "@emotion/react"
를 적용해야 react가 아닌 emotion 에서 jsx 함수를 가져온다.
이렇게 하면 @emotion/react/jsx-runtime
에서 _jsxRuntime
함수를 가져와 실행하는 것을 확인할 수 있다.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 2%, not dead",
"useBuiltIns": "usage",
"corejs": "3.37",
"shippedProposals": true
}
],
"@emotion/babel-preset-css-prop",
"@babel/preset-typescript",
["@babel/preset-react", { "runtime": "automatic", "importSource": "@emotion/react" }]
]
}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _jsxRuntime = require("@emotion/react/jsx-runtime");
var _react = require("@emotion/react");
...
const Test = () => {
return (0, _jsxRuntime.jsx)("div", {
css: _ref,
children: "Test"
});
};
var _default = exports.default = Test;
아래 실행 결과는 importSource를 명시하지 않았을 때의 트랜스파일 결과다. "runtime": "automatic" 이 적용되어 jsxRuntime 함수를 실행하지만 react에서 가져오는 것을 확인할 수 있다. 그래서 React를 참조하지 않아 오류는 나지 않지만 css가 적용되지 않는다.
{
"presets": [
[
...,
["@babel/preset-react", { "runtime": "automatic" }]
]
}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _jsxRuntime = require("react/jsx-runtime");
var _react = require("@emotion/react");
...
const Test = () => {
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
css: _ref,
children: "Test"
});
};
var _default = exports.default = Test;
이전에 물어봤던 팀원에게 블로그 글을 공유했더니 새로운 사실을 알게 되었다. 공식문서를 다시 읽어보니 @emotion/babel-preset-css-prop
은 jsx를 classic 방식으로 변환했을 때 사용하는 것이였다. 최상단에 적혀있는데 영어로 되어있어서 사용법만 찾다보니 이렇게 되었다. 앞으로는 공식문서를 더 꼼꼼히 읽어야겠다....
@emotion/babel-plugin
을 적용하니 var _react = require("@emotion/react");
라는 불필요한 import가 사라졌고 정상적으로 동작한다.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 2%, not dead",
"useBuiltIns": "usage",
"corejs": "3.37",
"shippedProposals": true
}
],
"@babel/preset-typescript",
["@babel/preset-react", { "runtime": "automatic", "importSource": "@emotion/react" }]
],
"plugins": ["@emotion"]
}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _jsxRuntime = require("@emotion/react/jsx-runtime");
...
const Test = () => {
return (0, _jsxRuntime.jsx)("div", {
css: {
color: 'hotpink',
'&:hover': {
color: 'darkorchid'
}
},
children: "Test"
});
};
var _default = exports.default = Test;
@emotion/babel-preset-css-prop
을 preset/react와 presset/typescript보다 앞에 배치해서 해결
@emotion/babel-plugin
을 설정하여 해결
emotion 공식 문서를 읽었을 때 @emotion/babel-preset-css-prop
가 preset-react, preset-typescript 이후에 추가되어야한다고 해석하여 이게 문제가 될 거라고 생각을 못했는데, 앞에 위치하니까 해결되었다.
영어 해석 문제인지 모르겠지만 디버깅하는 과정을 통해 babel이 jsx를 해석하는 코드를 알 수 있었고, importSource 속성의 유무에 따라 변경되는 부분을 확인할 수 있었다.
@emotion/babel-preset-css-prop
includes the emotion plugin. The@emotion/babel-plugin
entry should be removed from your config and any options moved to the preset. If you use@babel/preset-react
or@babel/preset-typescript
ensure that@emotion/babel-preset-css-prop
is inserted after them in your babel config.
https://emotion.sh/docs/@emotion/babel-preset-css-prop
https://emotion.sh/docs/@emotion/babel-plugin