πŸ“¦ Parcel

2woongjae@gmail.com

Mark Lee (μ΄μ›…μž¬)

  • Studio XID inc. ProtoPie Engineer
  • Seoul.JS μ˜€κ±°λ‚˜μ΄μ €
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν•œκ΅­ μœ μ € κ·Έλ£Ή μ˜€κ±°λ‚˜μ΄μ €
  • μΌλ ‰νŠΈλ‘  ν•œκ΅­ μœ μ € κ·Έλ£Ή μš΄μ˜μ§„
  • Seoul.js μ˜€κ±°λ‚˜μ΄μ €
  • Microsoft MVP - Visual Studio and Development Technologies
  • Code Busking with Jimmy
    • https://www.youtube.com/channel/UCrKE8ihOKHxYHBzI0Ys-Oow
  • λΆˆκ½ƒ νŠ€κ²Œ λΉ λ₯΄κ³ 
  • 섀정이 ν•„μš” μ—†λŠ”
  • μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ²ˆλ“€λŸ¬

λΆˆκ½ƒ νŠ€κ²Œ λΉ λ₯΄κ³ 

parcel/src/WorkerFarm.js

function getNumWorkers() {
  let cores;
  try {
    cores = require('physical-cpu-count');
  } catch (err) {
    cores = os.cpus().length;
  }
  return cores || 1;
}

parcel/src/WorkerFarm.js

class WorkerFarm extends Farm {
  constructor(options) {
    let opts = {
      autoStart: true,
      maxConcurrentWorkers: getNumWorkers()
    };

    super(opts, require.resolve('./worker'));

    this.localWorker = this.promisifyWorker(require('./worker'));
    this.remoteWorker = this.promisifyWorker(this.setup(['init', 'run']));

    this.started = false;
    this.init(options);
  }
  ...
}

parcel/src/worker.js

require('v8-compile-cache');
const Parser = require('./Parser');

let parser;

exports.init = function(options, callback) {
  parser = new Parser(options || {});
  Object.assign(process.env, options.env || {});
  callback();
};

exports.run = async function(path, pkg, options, callback) {
  try {
    var asset = parser.getAsset(path, pkg, options);
    await asset.process();

    callback(null, {
      dependencies: Array.from(asset.dependencies.values()),
      generated: asset.generated,
      hash: asset.hash,
      cacheData: asset.cacheData
    });
  } catch (err) {
    let returned = err;

    if (asset) {
      returned = asset.generateErrorMessage(returned);
    }

    returned.fileName = path;
    callback(returned);
  }
};

parcel/src/FSCache.js

const fs = require('./utils/fs');
const path = require('path');
const md5 = require('./utils/md5');
const objectHash = require('./utils/objectHash');
const pkg = require('../package.json');

// These keys can affect the output, so if they differ, the cache should not match
const OPTION_KEYS = ['publicURL', 'minify', 'hmr'];

class FSCache {
  constructor(options) {
    this.dir = path.resolve(options.cacheDir || '.cache');
    this.dirExists = false;
    this.invalidated = new Set();
    this.optionsHash = objectHash(
      OPTION_KEYS.reduce((p, k) => ((p[k] = options[k]), p), {
        version: pkg.version
      })
    );
  }

  async ensureDirExists() {
    await fs.mkdirp(this.dir);
    this.dirExists = true;
  }

  getCacheFile(filename) {
    let hash = md5(this.optionsHash + filename);
    return path.join(this.dir, hash + '.json');
  }

  async writeDepMtimes(data) {
    // Write mtimes for each dependent file that is already compiled into this asset
    for (let dep of data.dependencies) {
      if (dep.includedInParent) {
        let stats = await fs.stat(dep.name);
        dep.mtime = stats.mtime.getTime();
      }
    }
  }

  async write(filename, data) {
    try {
      await this.ensureDirExists();
      await this.writeDepMtimes(data);
      await fs.writeFile(this.getCacheFile(filename), JSON.stringify(data));
      this.invalidated.delete(filename);
    } catch (err) {
      console.error('Error writing to cache', err);
    }
  }

  async checkDepMtimes(data) {
    // Check mtimes for files that are already compiled into this asset
    // If any of them changed, invalidate.
    for (let dep of data.dependencies) {
      if (dep.includedInParent) {
        let stats = await fs.stat(dep.name);
        if (stats.mtime > dep.mtime) {
          return false;
        }
      }
    }

    return true;
  }

  async read(filename) {
    if (this.invalidated.has(filename)) {
      return null;
    }

    let cacheFile = this.getCacheFile(filename);

    try {
      let stats = await fs.stat(filename);
      let cacheStats = await fs.stat(cacheFile);

      if (stats.mtime > cacheStats.mtime) {
        return null;
      }

      let json = await fs.readFile(cacheFile);
      let data = JSON.parse(json);
      if (!await this.checkDepMtimes(data)) {
        return null;
      }

      return data;
    } catch (err) {
      return null;
    }
  }

  invalidate(filename) {
    this.invalidated.add(filename);
  }

  async delete(filename) {
    try {
      await fs.unlink(this.getCacheFile(filename));
      this.invalidated.delete(filename);
    } catch (err) {
      // Fail silently
    }
  }
}

module.exports = FSCache;

섀정이 ν•„μš” μ—†λŠ”

parcel-react-js/package.json

{
  "name": "parcel-react-js",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "parcel src/index.html",
    "build": "parcel build src/index.html --public-url '/'",
    "serve": "serve -s dist"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-preset-react-app": "^6.24.1",
    "parcel-bundler": "^1.4.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  },
  "dependencies": {
    "serve": "^6.4.4"
  }
}

parcel-react-js/.babelrc

{
  "presets": ["react-app"]
}

babel-preset-react/package.json

  "dependencies": {
    "babel-plugin-syntax-jsx": "^6.3.13",
    "babel-plugin-transform-react-display-name": "^6.23.0",
    "babel-plugin-transform-react-jsx": "^6.24.1",
    "babel-plugin-transform-react-jsx-self": "^6.22.0",
    "babel-plugin-transform-react-jsx-source": "^6.22.0",
    "babel-preset-flow": "^6.23.0"
  }

babel-preset-react-app/package.json

  "dependencies": {
    "babel-plugin-dynamic-import-node": "1.1.0",
    "babel-plugin-syntax-dynamic-import": "6.18.0",
    "babel-plugin-transform-class-properties": "6.24.1",
    "babel-plugin-transform-object-rest-spread": "6.26.0",
    "babel-plugin-transform-react-constant-elements": "6.23.0",
    "babel-plugin-transform-react-jsx": "6.24.1",
    "babel-plugin-transform-react-jsx-self": "6.22.0",
    "babel-plugin-transform-react-jsx-source": "6.22.0",
    "babel-plugin-transform-regenerator": "6.26.0",
    "babel-plugin-transform-runtime": "6.23.0",
    "babel-preset-env": "1.6.1",
    "babel-preset-react": "6.24.1"
  }

parcel-react-ts/package.json

{
  "name": "parcel-react-ts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "parcel src/index.html",
    "build": "parcel build src/index.html --public-url '/'",
    "serve": "serve -s dist"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^16.0.34",
    "@types/react-dom": "^16.0.3",
    "parcel-bundler": "^1.4.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "typescript": "^2.6.2"
  },
  "dependencies": {
    "serve": "^6.4.4"
  }
}

parcel-react-ts/tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "jsx": "react",
    "strict": true
  }
}
  • 애셋을 κΈ°μ€€μœΌλ‘œ 함.
    • ν•˜λ‚˜μ˜ JS 파일, CSS 파일 λ“± 각각의 파일
  • (λ‹Ήμ—°νžˆ) JS, TS νŒŒμΌμ— JS, TS κ°€ μ•„λ‹Œ μ• μ…‹ λ˜ν•œ μž„ν¬νŠΈ κ°€λŠ₯.
  • CSS λ‚˜ 이미지 파일 κ°€λŠ₯.
  • ν•˜μ§€λ§Œ, μ›ΉνŒ©μ²˜λŸΌ 인라인화 λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • parcel 의 μ§„μž… νŒŒμΌμ€ 주둜 HTML
  • λ‹€λ₯Έ νŽ˜μ΄μ§€λ‘œμ˜ 링크λ₯Ό μ œκ³΅ν•˜κΈ°μœ„ν•΄ JS 도 κ°€λŠ₯
  1. 에셋 트리 μž‘μ„±
  2. λ²ˆλ“€ 트리 μž‘μ„±
  3. νŒ¨ν‚€μ§•

ν”„λ‘œλ•μ…˜ λΉŒλ“œ μ˜΅μ…˜

  • --out-dir, -d
    • parcel build index.html --out-dir build
    • parcel build entry.js -d build
  • --public-url
    • parcel build index.html --public-url '/'
  • --no-minify
    • parcel build index.html --no-minify
  • --no-cache
    • parcel build index.html --no-cache

πŸ“¦ Parcel

By Woongjae Lee

πŸ“¦ Parcel

μ½”λ“œλ²„μŠ€ν‚Ή 팟캐슀트 1회

  • 1,079