From ef9967e0e5c95ef3054f1c280daf09e9a25e3f18 Mon Sep 17 00:00:00 2001 From: YukJiSoo Date: Thu, 19 Dec 2019 16:12:08 +0900 Subject: [PATCH 1/6] =?UTF-8?q?chore:=20bundle=20=EB=90=9C=20code=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EB=A5=BC=20=EC=9C=84=ED=95=9C=20react=20helm?= =?UTF-8?q?et=20=EB=AA=A8=EB=93=88=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 동적으로 번들링 된 코드를 추가해주기 위해 react helmet 모듈을 설치했습니다. --- coconut/package.json | 9 +++++---- coconut/yarn.lock | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/coconut/package.json b/coconut/package.json index 8c7e3663..7134b618 100644 --- a/coconut/package.json +++ b/coconut/package.json @@ -11,18 +11,19 @@ "react": "^16.12.0", "react-app-rewired": "^2.1.5", "react-dom": "^16.12.0", + "react-helmet": "^5.2.1", "react-router-dom": "^5.1.2", "react-scripts": "3.3.0", "styled-components": "^4.4.1", "worker-loader": "^2.0.0" }, "devDependencies": { - "prettier": "^1.19.1", + "@commitlint/cli": "^8.2.0", + "@commitlint/config-conventional": "^8.2.0", "eslint-config-prettier": "^6.5.0", - "lint-staged": "^9.4.2", "husky": "^3.0.9", - "@commitlint/cli": "^8.2.0", - "@commitlint/config-conventional": "^8.2.0" + "lint-staged": "^9.4.2", + "prettier": "^1.19.1" }, "scripts": { "start": "NODE_PATH=src react-app-rewired start", diff --git a/coconut/yarn.lock b/coconut/yarn.lock index 814cc6c1..14db0e71 100644 --- a/coconut/yarn.lock +++ b/coconut/yarn.lock @@ -9217,6 +9217,21 @@ react-error-overlay@^6.0.4: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.4.tgz#0d165d6d27488e660bc08e57bdabaad741366f7a" integrity sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA== +react-fast-compare@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-helmet@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.1.tgz#16a7192fdd09951f8e0fe22ffccbf9bb3e591ffa" + integrity sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA== + dependencies: + object-assign "^4.1.1" + prop-types "^15.5.4" + react-fast-compare "^2.0.2" + react-side-effect "^1.1.0" + react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.12.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" @@ -9311,6 +9326,13 @@ react-scripts@3.3.0: optionalDependencies: fsevents "2.1.2" +react-side-effect@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae" + integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w== + dependencies: + shallowequal "^1.0.1" + react@^16.12.0: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" @@ -10044,6 +10066,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" From 27129ded6942befdd461463bae575a8545b0b447 Mon Sep 17 00:00:00 2001 From: YukJiSoo Date: Thu, 19 Dec 2019 16:23:43 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20build=EB=A5=BC=20=EC=A0=84=EB=8B=B4?= =?UTF-8?q?=ED=95=98=EB=8A=94=20worker=20=20=ED=8C=8C=EC=9D=BC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build 과정을 진행하고 bundle 된 코드를 반환하는 worker 파일을 작성했습니다. --- coconut/src/worker/build.worker.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 coconut/src/worker/build.worker.js diff --git a/coconut/src/worker/build.worker.js b/coconut/src/worker/build.worker.js new file mode 100644 index 00000000..c5fdc898 --- /dev/null +++ b/coconut/src/worker/build.worker.js @@ -0,0 +1,25 @@ +import * as bundler from 'bundler'; + +self.process = {}; +self.process.env = {}; +self.process.env.NODE_MODULE = 'development'; + +self.exports = {}; + +self.addEventListener('message', ({ data: { fileSystem } }) => { + self.fileSystem = fileSystem; + self.bundledCode = ''; + + buildProject(); +}); + +function buildProject() { + try { + bundler.init(); + bundler.require('./index.js'); + + self.postMessage({ bundledCode: self.bundledCode }); + } catch (error) { + self.postMessage({ error: error.stack }); + } +} From aa05d4dd8baecfa4beca0cd213a26ddff7bd23c4 Mon Sep 17 00:00:00 2001 From: YukJiSoo Date: Thu, 19 Dec 2019 16:26:59 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20worker=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=97=90=20=EB=94=B0=EB=A5=B8=20bundler=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit worker와 main 쓰레드에서 사용하는 require, pathparser의 내용이 근소하게 다릅니다. 이 때문에 이를 분리해주었습니다. --- coconut/src/bundler/codeTemplate.js | 49 +++++++++++++++++++++++++ coconut/src/bundler/pathParser.js | 4 +- coconut/src/bundler/pathParserInMain.js | 28 ++++++++++++++ coconut/src/bundler/require.js | 32 ++++------------ coconut/src/bundler/requireInMain.js | 32 ++++++++++++++++ 5 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 coconut/src/bundler/codeTemplate.js create mode 100644 coconut/src/bundler/pathParserInMain.js create mode 100644 coconut/src/bundler/requireInMain.js diff --git a/coconut/src/bundler/codeTemplate.js b/coconut/src/bundler/codeTemplate.js new file mode 100644 index 00000000..747abb61 --- /dev/null +++ b/coconut/src/bundler/codeTemplate.js @@ -0,0 +1,49 @@ +const scriptCodeTemplate = (code, newPath, parentPath) => /*javascript*/ ` +exports['${newPath}'] = (() => { + window.pathStack = []; + pathStack.push('${parentPath}'); + + let exports = {}; + let module = { exports: {} }; + + try { + ${code} + return Object.keys(exports).length ? exports : module.exports; + } catch (e) { + const ignoreErrorList = [ + 'Cannot redefine property', + 'Cannot read property', + 'Cannot set property default of # which has only a getter' + ]; + const errorType = e.message; + const isExistInIgnoreList = ignoreErrorList.some(ignoreError => + errorType.startsWith(ignoreError) + ); + + if(!isExistInIgnoreList) throw e; + } +})()`; + +const executeCodeTemplate = code => /*javascript*/ ` +(() => { + let exports = {}; + try { + ${code} + return Object.keys(exports).length ? exports : module.exports; + } catch (e) { + const ignoreErrorList = [ + 'Cannot redefine property', + 'Cannot read property', + 'Cannot set property default of # which has only a getter', + 'document is not defined' + ]; + const errorType = e.message; + const isExistInIgnoreList = ignoreErrorList.some(ignoreError => + errorType.startsWith(ignoreError) + ); + + if(!isExistInIgnoreList) throw e; + } +})()`; + +export { scriptCodeTemplate, executeCodeTemplate }; diff --git a/coconut/src/bundler/pathParser.js b/coconut/src/bundler/pathParser.js index 59d124e2..8a5cedd1 100644 --- a/coconut/src/bundler/pathParser.js +++ b/coconut/src/bundler/pathParser.js @@ -15,10 +15,10 @@ function pathParser(param) { if (extension[extension.length - 1] === 'js') { return [param, path.dirname(param)]; } - if (window.fileSystem[`${param}.js`]) { + if (self.fileSystem[`${param}.js`]) { return [`${param}.js`, path.dirname(param)]; } - if (window.fileSystem[`${param}/index.js`]) { + if (self.fileSystem[`${param}/index.js`]) { return [`${param}/index.js`, param]; } diff --git a/coconut/src/bundler/pathParserInMain.js b/coconut/src/bundler/pathParserInMain.js new file mode 100644 index 00000000..a079240c --- /dev/null +++ b/coconut/src/bundler/pathParserInMain.js @@ -0,0 +1,28 @@ +import path from 'path'; + +const DEPENDENCY_PATH = '/node_modules/'; + +function pathParser(param) { + const moduleName = param; + + if (param[0] !== '.' && param[0] !== '/') { + param = `${DEPENDENCY_PATH}${param}`; + } + + param = path.resolve(window.pathStack[window.pathStack.length - 1], param); + const extension = param.split('.'); + + if (extension[extension.length - 1] === 'js') { + return [param, path.dirname(param)]; + } + if (window.fileSystem[`${param}.js`]) { + return [`${param}.js`, path.dirname(param)]; + } + if (window.fileSystem[`${param}/index.js`]) { + return [`${param}/index.js`, param]; + } + + throw Error(`Module not found: '${moduleName}'`); +} + +export { pathParser }; diff --git a/coconut/src/bundler/require.js b/coconut/src/bundler/require.js index b79e416c..b9495099 100644 --- a/coconut/src/bundler/require.js +++ b/coconut/src/bundler/require.js @@ -1,37 +1,17 @@ import { pathStack } from './global'; import { pathParser } from './pathParser'; import { transformCode } from './core'; - -const executeCodeTemplate = code => /*javascript*/ ` -(() => { - let exports = {}; - try { - ${code} - return Object.keys(exports).length ? exports : module.exports; - } catch (e) { - const ignoreErrorList = [ - 'Cannot redefine property', - 'Cannot read property', - 'Cannot set property default of # which has only a getter' - ]; - const errorType = e.message; - const isExistInIgnoreList = ignoreErrorList.some(ignoreError => - errorType.startsWith(ignoreError) - ); - - if(!isExistInIgnoreList) throw e; - } -})()`; +import { scriptCodeTemplate, executeCodeTemplate } from './codeTemplate'; function require(path) { if (path === '.' || path === './') throw Error('Recursive path parsing error'); const [newPath, newPathParent] = pathParser(path); - if (window.exports[newPath]) return window.exports[newPath]; + if (self.exports[newPath]) return self.exports[newPath]; pathStack.push(newPathParent); - const code = transformCode(window.fileSystem[newPath].contents).value; + const code = transformCode(self.fileSystem[newPath].contents).value; let result = null; let stackLength = 0; @@ -43,8 +23,12 @@ function require(path) { result = eval(executeCodeTemplate(code)); } - window.exports[newPath] = result; + self.bundledCode = ` + ${self.bundledCode}\n${scriptCodeTemplate(code, newPath, newPathParent)}`; + + self.exports[newPath] = result; pathStack.pop(); + return result; } diff --git a/coconut/src/bundler/requireInMain.js b/coconut/src/bundler/requireInMain.js new file mode 100644 index 00000000..892bdf8c --- /dev/null +++ b/coconut/src/bundler/requireInMain.js @@ -0,0 +1,32 @@ +import { pathStack } from './global'; +import { pathParser } from './pathParserInMain'; +import { transformCode } from './core'; +import { executeCodeTemplate } from './codeTemplate'; + +function requireInMain(path) { + if (path === '.' || path === './') + throw Error('Recursive path parsing error'); + const [newPath, newPathParent] = pathParser(path); + + if (window.exports[newPath]) return window.exports[newPath]; + + pathStack.push(newPathParent); + const code = transformCode(window.fileSystem[newPath].contents).value; + + let result = null; + let stackLength = 0; + try { + stackLength = pathStack.length; + result = eval(executeCodeTemplate(code)); + } catch (error) { + while (stackLength < pathStack.length) pathStack.pop(); + result = eval(executeCodeTemplate(code)); + } + + window.exports[newPath] = result; + pathStack.pop(); + + return result; +} + +export { requireInMain }; From 6ad4c59e2c4450d5f49ed83a4a8fe13ee598f9b4 Mon Sep 17 00:00:00 2001 From: YukJiSoo Date: Thu, 19 Dec 2019 16:29:49 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20worker=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20coconut=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=99=80=20hook=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build worker를 적용하기 위해 coconut component와 hook을 수정해주었습니다. --- coconut/src/components/Coconut/index.js | 20 +++++++++++-- coconut/src/hooks/useBuildProject.js | 38 +++++++++++-------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/coconut/src/components/Coconut/index.js b/coconut/src/components/Coconut/index.js index d7ac8aae..93af486d 100644 --- a/coconut/src/components/Coconut/index.js +++ b/coconut/src/components/Coconut/index.js @@ -1,5 +1,6 @@ import React, { useState, useEffect, useReducer, useCallback } from 'react'; import { useParams } from 'react-router-dom'; +import { Helmet } from 'react-helmet'; import * as Styled from './style'; import CoconutSpinner from 'components/CoconutSpinner'; @@ -15,6 +16,9 @@ import { import ProjectReducer from 'reducers/ProjectReducer'; +import BuildWorker from 'worker/build.worker.js'; +import { requireInMain } from 'bundler/requireInMain'; + const PROJECT_IDBNAME = 'Coconut'; const DEPENDENCY_IDBNAME = 'Dependency'; @@ -57,6 +61,9 @@ const buildProjectFailState = errorPhrase => ({ function Coconut() { const { projectId } = useParams(); + const [worker] = useState(new BuildWorker()); + const [innerCode, setInnerCode] = useState(''); + const [buildState, setBuildState] = useState(initilaBuildState); const [project, dispatchProject] = useReducer(ProjectReducer, undefined); @@ -85,6 +92,7 @@ function Coconut() { return; } + window.require = requireInMain; return () => closeConnections(); }, [iDBConnectionState, closeConnections]); @@ -112,19 +120,22 @@ function Coconut() { return; } - buildProject(project); - }, [dependencyState, buildProject, project]); + buildProject(project, worker); + }, [dependencyState, buildProject, project, worker]); const handleBuildProject = useCallback(() => { if (!buildResult) return; - const { error } = buildResult; + const { error, bundledCode } = buildResult; const resultBuildState = error ? buildProjectFailState(error) : successBuildState; setBuildState(resultBuildState); sendToCocode({ command: BUILD_END }); + if (error) return; + + setInnerCode(bundledCode); }, [buildResult, sendToCocode]); useEffect(handleConnectToIDB, [iDBConnectionState]); @@ -142,6 +153,9 @@ function Coconut() { )}
+ + + ); } diff --git a/coconut/src/hooks/useBuildProject.js b/coconut/src/hooks/useBuildProject.js index 802e87dc..c1a113af 100644 --- a/coconut/src/hooks/useBuildProject.js +++ b/coconut/src/hooks/useBuildProject.js @@ -1,10 +1,25 @@ import { useState, useCallback } from 'react'; -import * as bundler from 'bundler'; - function useBuildProject() { const [buildResult, setBuildResult] = useState(undefined); + const buildProject = (project, worker) => { + const { files, root } = project; + + const rootPath = files[root].path; + fileParser(project, rootPath, root); + + worker.postMessage({ + command: 'start', + fileSystem: window.fileSystem + }); + + worker.onmessage = ({ data: { error, bundledCode } }) => { + if (!error) setBuildResult({ bundledCode }); + else setBuildResult({ error }); + }; + }; + const fileParser = useCallback((project, path, id) => { const { files } = project; @@ -21,25 +36,6 @@ function useBuildProject() { } }, []); - const buildProject = useCallback( - project => { - const { files, root } = project; - - const rootPath = files[root].path; - if (project) fileParser(project, rootPath, root); - - try { - bundler.init(); - bundler.require('./index.js'); - - setBuildResult({ error: undefined }); - } catch (error) { - setBuildResult({ error: error.stack }); - } - }, - [fileParser, setBuildResult] - ); - return [buildResult, buildProject]; } From 8a84eaccf1692bb0143f76cfe62e4fcddb5d18ae Mon Sep 17 00:00:00 2001 From: YukJiSoo Date: Thu, 19 Dec 2019 16:59:51 +0900 Subject: [PATCH 5/6] =?UTF-8?q?chore:=20babel=20transform=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=97=90=20option=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit transpiling시 나오는 warning을 없애고 최적화를 위해 옵션을 추가해주었습니다. --- coconut/src/bundler/core.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coconut/src/bundler/core.js b/coconut/src/bundler/core.js index c873413a..492b7936 100644 --- a/coconut/src/bundler/core.js +++ b/coconut/src/bundler/core.js @@ -6,7 +6,9 @@ import { pathStack } from './global'; function transformCode(code) { try { const result = babel.transform(code, { - presets: [presetEnv, presetReact] + presets: [presetEnv, presetReact], + compact: true, + minified: true }); return { state: true, From 3ff72008f00e54bf3da27c7848fba77b2f931d0e Mon Sep 17 00:00:00 2001 From: YukJiSoo Date: Fri, 20 Dec 2019 03:50:25 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9D=B8=EC=9E=90=20=EB=84=98=EA=B8=B0=EB=8D=98=20?= =?UTF-8?q?=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit worker에게 불필요한 인자를 넘기던 것을 제거했습니다. --- coconut/src/hooks/useBuildProject.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/coconut/src/hooks/useBuildProject.js b/coconut/src/hooks/useBuildProject.js index c1a113af..3d5ba143 100644 --- a/coconut/src/hooks/useBuildProject.js +++ b/coconut/src/hooks/useBuildProject.js @@ -9,10 +9,7 @@ function useBuildProject() { const rootPath = files[root].path; fileParser(project, rootPath, root); - worker.postMessage({ - command: 'start', - fileSystem: window.fileSystem - }); + worker.postMessage({ fileSystem: window.fileSystem }); worker.onmessage = ({ data: { error, bundledCode } }) => { if (!error) setBuildResult({ bundledCode });