npm docs
npm run에 대해서는 상기 링크에 다음과 같이 기재되어 있다
npm run은 package.json의 scripts객체에서 임의의 명령을 실행한다.
npm run-script < command > [--if-present][--silent] [-- ]
npm run-script < command > [--workspace=]
npm run-script < command > [--workspaces]
run-script
에서 -script
를 생략하고 run
만 기입해도 된다.
위 링크에서 언급한 대표적인 동작의 코드는 다음과 같다.
npm/lifecycle.js
/*
* part of
* npm/lifecycle.js
*/
function runCmd_ (cmd, pkg, env, wd, stage, unsafe, uid, gid, cb_) {
function cb (er) {
cb_.apply(null, arguments)
log.resume()
process.nextTick(dequeue)
}
//conf : 환경변수등을 갖고 있음
var conf = {
cwd: wd,
env: env,
stdio: [ 0, 1, 2 ]
}
if (!unsafe) {
conf.uid = uid ^ 0
conf.gid = gid ^ 0
}
/* Windows외의 운영체제 :
* 쉘을 사용하며, 추가적인 커맨드는 -c이다.
* -c는 현재 프로그램에서 이후 커맨드를 실행하라는 뜻이다.
*/
var sh = 'sh'
var shFlag = '-c'
/* Windows에서의 운영체제 :
* cmd (명령 프롬프트)를 사용하며, 추가적인 커맨드는 /d /s /c이다.
* /d : 자동 실행 명령의 실행을 사용하지 않음
* /s : /c, /k뒤의 문자열 처리를 수정함
* /c : 문자열에 지정된 명령을 수행하고 중지함
*
*/
if (process.platform === 'win32') {
sh = process.env.comspec || 'cmd'
shFlag = '/d /s /c'
conf.windowsVerbatimArguments = true
}
log.verbose('lifecycle', logid(pkg, stage), 'PATH:', env[PATH])
log.verbose('lifecycle', logid(pkg, stage), 'CWD:', wd)
log.silly('lifecycle', logid(pkg, stage), 'Args:', [shFlag, cmd])
var progressEnabled = log.progressEnabled
if (progressEnabled) log.disableProgress()
/*
* spawn 함수
* node.js에서 사용하는 함수이다.
*
* child_process.spawn(command[, args][, options])
*
* 명령줄의 argument와 함께 새 프로세스를 생성한다.
*/
var proc = spawn(sh, [shFlag, cmd], conf)
위 lifecycle.js 코드를 참조하면 다음과 같다.
runPackageLifeCycle
, 혹은 dequeue
시 runCmd
를 호출한다.dequeue
로 호출할 경우 queue.shift()
를 인수(argument)로 받는다. 최종적으로 apply(null, queue.shift())
한다. (this는 신경쓰지 않고(null), queue에서 받은것만 인자로서 호출)runCmd
는 현재 무언가 하고 있는지 확인한다. 무언가 하고 있다면(running
), 지금 인자의 배열로 받은 Cmd
를 다시 갖고 있는 queue(그냥 list이다)
의 맨 끝에 다시 넣어버린다. (push
) 결국 현재 무언가를 하고 있으면 다른 것은 하지 못한다. (스레드적인 동작을 하지는 않는다.)running
을 true
로 만든 뒤 safe여부를 확인한다. 플랫폼이 win32
라면 unsafe
하다. uid
, gid
를 0으로 한다.uid
, gid
를 uidNumber
함수를 사용하여 얻어낸다.runCmd_
를 실행한다.runCmd_
는 프로세스를 spawn
한다.단, npm-lifecycle
은 npm v7이전에만 유효하며 npm v7이후로는 npmcli/run-script
를 사용한다. 현재(2021.11 기준)는 npm v8 이상수준이다.
run-script는 npm-lifecycle에서 다음을 만족하는 구현이다.
RFC 90
라이프사이클 스크립트 environment 사이즈 줄이기RFC 77
npm install시 굳이 알 필요 없는 로그는 생략RFC 73
npm run시 목표를 찾지 못했을 때, 디렉토리의 루트까지 올라가봄- 모던한 코딩 테크닉(실제로 화살표 함수나 const, Promise등의 더 모던한 코딩 테크닉을 사용함)과 더 나은 테스트 커버리지로 새로고침된 코드베이스
run-script에서는 다음과 같이 동작한다.
event(install/run)
, argument(인수)
와 path(실행할 패키지 path)
, npm_package_from
, ..._resolved
, ..._integrity
를 포함한 environment
, 실행할 scriptShell(shell이나 cmd의 경로/디폴트 있음)
등을 갖고 있는 객체인 option
을 주면서 실행한다.
0번에서 호출할때 준 option
을 validate체크
(타입이 안맞으면 에러를 throw함)하고, 문제 없이 넘어갔다면 options
들 중에서 pkg
와 path
를 const
로 따로 갖고 온다.
const
로 가져오기 때문에 pkg
와 path
값은 바뀌지 않음을 보장한다.
pkg
가 null
이 아니라면 runScriptPkg
를 options
를 인자로 가져오며 실행시키고, null
이라면 read-package-json-fast
라는 패키지에게 path
와 'package.json'을 결합한 string
을 건네며 비동기식
실행한다(프로젝트 파일을 읽기 위하여 결국 파일 I/O하여야 하기 때문). promise
타입이며, 성공했다면 option
과 pkg
를 엮은 객체를 인수로 건네며 runScriptPkg
를 수행한다.
read-package-json-fast
node_modules
트리에서package.json
파일을 읽는데에만 최적화된 작은 패키지이다.
runScriptPkg
에서는 받아온 pkg
로부터 scripts
와 gypfile
을 받아오며, cmd
를 여러 방식으로 결정한다, 그 이후 promiseSpawn
을 options
로부터 받아오거나, 함수 자체적으로 설정하였던 cmd
등을 이용하여 makeSpawnArgs
를 호출하고, 그 결과를 인자로서 실행한다.
최종적으로 수행하는 promiseSpawn
은 명령을 수행하고, 프로세스 결과에 따라 resolve/reject하는 프로미스
를 반환하는 함수이다.
최종적으로 package.json에 존재하는 scripts에 존재하는 key를 spawn하는 함수로 볼 수 있다.
(여기서는 parcel만 다룬다. 또다른 번들러 webpack의 경우)
package.json에 존재하는 scripts의 key의 예시는 다음과 같다.
"scripts": {
"dev": "parcel ui_sample.html",
"build": "parcel build index.html",
"test": "jest"
},
위 package.json
의 scripts
는 parcel
명령어(parcel-bundler
)를 사용한다.
parcel-bundler
는 npm
에서 설치할 수 있는 패키지의 하나이다.
(parcel-bundler
자체는 v1이며, deprecated되었다.)
현재는 v2인 parcel
을 사용한다. npm parcel link
위에서 사용하는 parcel
cli명령어
의 링크는 다음과 같다. parcel document
parcel
의 명령은 serve(default)
/ watch
/ build
로 되어있다.
serve
명령은 파일을 변경할 때(edit후 save할 때) 앱을 자동으로 다시 빌드하고 핫 리로딩
을 지원하는 개발 서버
를 시작한다.default
이므로 명령 없이 사용할 수 있다. parcel src/index.html
이 명령(serve)
을 사용하면 parcel
의 내장 개발 서버
가 자동으로 실행된다. 기본적으로 http://localhost:1234
에서 서버를 시작한다.
-p
, --port
를 통하여 이 포트를 따로 지정할 수 있다.localhost:8080
에서 진행된다.--open
을 통해 수행 즉시 기본 브라우저에서 항목을 자동으로 연다. 브라우저 이름을 전달하여 다른 브라우저를 열 수도 있다.npm run dev를 수행하면, npm
은 run
명령에 맞게 packages.json
에 존재하는 scripts
중 dev
키를 찾아서 그것을 promiseSpawn
(새로운 프로세스를 수행, 프로미스 리턴)한다.
dev
가 parcel
명령어인 경우, parcel
은 cli
명령어등을 참고하여 port
등을 지정하고, (없으면 localhost:1234
) 해당 설정에 맞는 개발 서버
를 오픈한다.
따라서 개발 서버는 parcel
에 의한 것이다.
npm
에 의한 것도, node.js
에 의한 것도 아니다.
webpack
의 경우 webpack-dev-server를 설치하여 사용하는 것으로 사용할 수 있다.
parcel문서
에서 parcel
은 잠금파일 (~~.lock) 파일을 기반으로 패키지 관리자를 자동으로 감지
하는데, 잠금파일이 없으면 시스템에 설치된 항목 중 우선순위에 따라 패키지 관리자가 선택된다. 그런데 우선순위가 Yarn
, Pnpm
, npm
순이다.Yarn
이 제일 높은것으로 보아 parcel은 Yarn
을 우선적으로 작업하는것으로 보인다.