๐Ÿ“ฆ 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,172