簡介
Blazing fast, zero configuration web application bundler.官網 github
快速,零配置的 Web 應用程序打包器。
主要有以下特點:
- 快速打包: 利用cpu多核,和緩存機制打包速度更快
-
自動轉換: 支持目前絕大部份資源文件的轉換 支持文件類型
-
postCss
,postHtml 以及各種對應插件與配置 - babel,
typescript
,coffeeScript,等JS預編譯 - sass,less,
stylus
等CSS預編譯語言 - 支持 vue,
react
,angular 主流前端框架 -
yaml
,json,以及各種圖片資源
-
-
打包所有資源: 支持
brower
| node | electron 環境,項目內所引用的資源文件會全部編譯打包 - 零配置代碼拆分: 基本無任何配置即可以開發,支持代碼動態加載,自動拆分打包文件
- 模塊熱替換: 支持代碼改動支持熱更新,socket實時無刷新更新web頁面
- 友好的錯誤記錄: 支持不同級別日志顯示,默認只會顯示錯誤信息
其他打包工具對比
-
grunt
node老牌的主流的構建工具,需要使用gruntfile編寫構建任務,然后運行grunt-cli命令執行任務,功能強大,擁有大量的可用插件。但是編寫構建任務復雜、繁重,會生成構建中間文件,學習成本高
。 -
fis3
是百度公司內部使用的構建工具,后來開源了,文檔相當齊全,也需要fisconfig配置文件。由于百度團隊集成了常用的一些前端構建任務的插件,配置簡單方便。但是現在基本不維護了
,對于node新版本支持也不是非常好。 -
gulp
node基于流的自動化構建工具。需要使用gulpfile編寫構建任務
,然后運行gulp命令執行任務。相比grunt易于使用,構建快速,易于學習,插件高質,不產生中間文件。 -
webpack
webpack是一個現代 JavaScript 應用程序的靜態模塊打包器,也是目前流行的打包工具,是的高度可配置的。從 webpack v4.0.0 開始,可以不用引入一個配置文件。配置內容相當齊全,性能高,也集成了各種個樣的插件?;緷M足了前端開發絕大部分需求。缺點就是配置相當的多
,不適合新手使用。 -
roadhog
是webpack上層封裝,本質上是簡化了一系列webpack的配置,生成最終的wepack配置文件,是antd-pro的官方的cli工具。它效果與parcel很像,適用范圍窄,打包效率低
。 -
create-react-app
集成了react開發的一系列資源,還有命令行工具,對于創建的新的react項目非常好,但是限制也是相當明顯的。只適用react
快速使用
- 下載全局依賴
npm install -g parcel-bundler
- 新建html,css,js文件
// index.html
<html>
<body>
<p>hello world</p>
<script src="./index.js"></script>
</body>
</html>
// index.js
import './index.css';
alert(1);
// index.css
body {
background: red;
}
- 本地開發
parcel serve index.html
- 打包應用
parcel build index.html
命令行選項介紹
- parcel主要命令
parcel -h # 查看幫助信息
parcel serve -h # 本地開發模式,會啟動一個默認1234端口的本地服務器,代理到配置的目標文件,前端頁面開發為主
parcel watch -h # 監聽文件改動模式,不會啟動默認本地服務器,服務端開發為主
parcel build -h # 編譯源文件并打包到目標?文件夾
- parcel重要選項介紹
- server、watch 命令一致
-p, --port <port> # ?本地服務器啟動端口
--open # 是否打開默認瀏覽器
--public-url # 靜態資源文件夾路徑
--no-hmr # false 是否開啟代碼熱更新
--no-cache # false 是否啟用緩存機制,用于緩存引起的代碼更新不及時的問題排查
--no-source-maps # false 是否啟用source-maps文件,主要用于錯誤排查與定位
--no-autoinstall # false 很多資源文件的loader包是自動下載的,代碼會將沒有路徑的包當成npm包自動下載,發現代碼自動下載不需要的npm包是,需要開啟這個屬性
-t, --target [target] # browser 代碼目標環境 node | browser | electron
--log-level <level> # 1 日志輸入級別 0 | 1 | 2 | 3 對應 不輸出日志 | 僅錯誤日志 | 警告與錯誤日志 | 所有日志
- build 命令
-d, --out-dir <path> # 打包目標文件夾 默認 "dist"
-o, --out-file <filename> # 項目入口文件的文件名,默認與 --public-url 一致
--no-minify # 打包時不壓縮源文件
--detailed-report # 打印詳細打包資源報告
主要使用場景實例--- 所有demo無需手動寫依賴與配置
由于parcel可以自動加載 vue,react...等框架的依賴包,所以不需要自己特地去將依賴加到package.json中,直接寫vue,react組件即可。
vue demo
- 編寫
src/app.vue
<template lang="html">
<div id="app">
<h1>Hello Parcel vue app e?“| e??€</h1>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style lang="css">
#app {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
h1 {
font-weight: 300;
}
</style>
- 編寫
src/main.js
import Vue from 'vue'
import App from './app.vue'
new Vue({
el: '#app',
render: h => h(App)
});
- 編寫
index.html
<html>
<head>
<title>Welcome to Vue</title>
</head>
<body>
<div id="app"></div>
<script src="src/main.js"></script>
</body>
</html>
- 運行與打包
# 運行
parcel serve pathto/index.html --no-cache
# 打包
parcel build pathto/index.html --public-url . --no-source-maps --no-cache --detailed-report
react-typescript demo
- 編寫
components/App.tsx
組件
import * as React from 'react'
export default class App extends React.Component<any, any> {
render() {
return (
<div className="container">
<h1>typescript react component</h1>
</div>
)
}
}
- 編寫
index.tsx
入口
import * as React from 'react'
import { render } from 'react-dom'
import App from './components/App'
render(<App />, document.getElementById('root'))
- 編寫
index.html
<html lang="en">
<head>
<title>Parcel with Typescript</title>
</head>
<body>
<div id="root"></div>
<script src="./index.tsx"></script>
</body>
</html>
- 開發與打包
parcel serve pathto/index.html --no-cache
# 打包
parcel build pathto/index.html --public-url . --no-source-maps --no-cache --detailed-report
多頁面應用
- 建立
pages
文件夾放html文件
// index.html
<html lang="en">
<head>
<title>index</title>
</head>
<body>
<nav>
<ul>
<li><a href="./page1.html">第一頁</a></li>
<li><a href="./page2.html">第二頁</a></li>
<li><a href="./page3.html">第三頁</a></li>
</ul>
</nav>
<h1>這是首頁</h1>
</body>
</html>
// page1.html
<html lang="en">
<head>
<title>Page 1</title>
</head>
<body>
<h1>第一頁</h1>
<a href="./index.html">返回首頁</a>
<script src="../js/page1.js"></script>
</body>
</html>
// page2.html
<html lang="en">
<head>
<title>Page 2</title>
</head>
<body>
<h1>第二頁</h1>
<a href="./index.html">返回首頁</a>
<script src="../js/page2.js"></script>
</body>
</html>
// page3.html
<html lang="en">
<head>
<title>Page 3</title>
</head>
<body>
<h1>第三頁</h1>
<a href="./index.html">返回首頁</a>
<script src="../js/page3.js"></script>
</body>
</html>
- 建立
css
文件夾放less文件
// base.less
body {
background: grey;
color: #ffffff;
}
// page1.less
body {
background: red !important;
}
// page2.less
body {
background: black !important;
}
// page3.less
body {
background: green !important;
}
- 建立
js
文件夾放js文件
// base.js
import '../css/base.less';
export const baseFunc = (text) => {
alert(`baseFunc --- by ${text}`);
}
// page1.js
import '../css/page1.less'
import { baseFunc } from './base'
baseFunc('page1');
// page2.js
import '../css/page2.less'
import { baseFunc } from './base'
baseFunc('page2');
// page3.js
import '../css/page3.less'
import { baseFunc } from './base'
baseFunc('page3');
- 開發與打包
注意這里使用 * 號匹配html路徑
# 開發
parcel serve pathto/pages/*.html --no-cache
# 打包
parcel build pathto/pages/*.html --public-url ./ --no-source-maps --no-cache --detailed-report
parcel原理簡介
parcel flow.jpg
寫一個parcel 識別 **.json2
文件parcel 插件
- 寫一個 Asset 實現類
myAsset.js
const path = require('path');
const json5 = require('json5');
const {minify} = require('terser');
const {Asset} = require('parcel-bundler');
class MyAsset extends Asset {
constructor(name, options) {
super(name, options);
this.type = 'js'; // set the main output type.
}
async parse(code) {
// parse code to an AST
return path.extname(this.name) === '.json5' ? json5.parse(code) : null;
}
// async pretransform() { // 轉換前
// // optional. transform prior to collecting dependencies.
// }
// collectDependencies() { // 分析依賴
// // analyze dependencies
// }
// async transform() { // 轉換
// // optional. transform after collecting dependencies.
// }
async generate() { // 生成代碼
// code generate. you can return multiple renditions if needed.
// results are passed to the appropriate packagers to generate final bundles.
let code = `module.exports = ${
this.ast ? JSON.stringify(this.ast, null, 2) : this.contents
};`;
if (this.options.minify && !this.options.scopeHoist) {
let minified = minify(code);
if (minified.error) {
throw minified.error;
}
code = minified.code;
}
return [{
type: 'json2',
value: this.contents
}, {
type: 'js',
value: code
}];
}
// async postProcess(generated) { // 生成代碼完成之后操作
// // Process after all code generating has been done
// // Can be used for combining multiple asset types
// }
}
module.exports = MyAsset;
- 寫一個 Packager 實現類
myPackager.js
const {Packager} = require('parcel-bundler');
class MyPackager extends Packager {
async start() { // 文件頭之前的內容
// optional. write file header if needed.
await this.dest.write(`\n123-before\n`);
}
async addAsset(asset) { // 文件內容
// required. write the asset to the output file.
await this.dest.write(`\n${asset.generated.json2}\n`);
}
async end() { // 寫在文件尾 的內容
// optional. write file trailer if needed.
await this.dest.end(`\nabc-after\n`);
}
}
module.exports = MyPackager;
- 編寫插件方法
myPlugin.js
module.exports = function (bundler) {
bundler.addAssetType('.josn2', require.resolve('./MyAsset'));
bundler.addPackager('json2', require.resolve('./MyPackager'));
};
- 發布到npm中
將這個包發不到npm時,需要加parcel-plugin-
前綴,然后它就會被parcel自動識別。 - 使用插件兩種方式
- 只需要將
parcel-plugin-
前綴的包,加入到package.json中,pacel在初始化的時候就會自動加載這些插件。 - 通過parcel類使用
const path = require('path');
const Bundler = require('parcel-bundler');
const bundler = new Bundler(file, options);
// 獲取node命令行的參數
const args = process.argv.splice(2);
// Entrypoint file location
const file = path.join(__dirname, './src/index.html');
// Bundler options
const options = {
outDir: './demo_custom/dist', // The out directory to put the build files in, defaults to dist
// outFile: './demo_custom/dist/index.html', // The name of the outputFile
// publicUrl: './demo_custom/dist', // The url to server on, defaults to dist
watch: true, // whether to watch the files and rebuild them on change, defaults to process.env.NODE_ENV !== 'production'
cache: false, // Enabled or disables caching, defaults to true
cacheDir: '.cache', // The directory cache gets put in, defaults to .cache
minify: true, // Minify files, enabled if process.env.NODE_ENV === 'production'
target: 'browser', // browser/node/electron, defaults to browser
https: false, // Serve files over https or http, defaults to false
logLevel: 3, // 3 = log everything, 2 = log warnings & errors, 1 = log errors
hmrPort: 0, // The port the HMR socket runs on, defaults to a random free port (0 in node.js resolves to a random free port)
sourceMaps: args[0] !== 'build', // Enable or disable sourcemaps, defaults to enabled (not supported in minified builds yet)
hmrHostname: '', // A hostname for hot module reload, default to ''
detailedReport: args[0] === 'build', // Prints a detailed report of the bundles, assets, filesizes and times, defaults to false, reports are only printed if watch is disabled
open: true,
port: 1234,
production: args[0] === 'build'
};
const runBundle = async () => {
// Initializes a bundler using the entrypoint location and options provided
const bundler = new Bundler(file, options);
bundler.addAssetType('.json2', require.resolve('./myAsset')); // 引入剛剛寫好的資源識別類 【識別xx.json2類型文件】
bundler.addPackager('json2', require.resolve('./myPackager')); // 引入剛剛寫好的打包類【打包 xx.json2 類型文件】
if (cli === 'serve' && options.open) {
const server = await bundler.serve(options.port);
if (server) {
await require('parcel-bundler/src/utils/openInBrowser')(`http://localhost:${options.port}`, true)
}
} else {
childProcess.exec(`rm -rf ${path.join(__dirname, './dist')}`);
bundler.bundle();
}
};
parcel實踐應用show-pages
該項目簡單二次應用了parcel 工具,使用了postcss,posthtml,以及一些模版替換的,純前端項目,已可以實現基本功能。該項目現已稍微擱置,沒有進步繼續編寫。后邊有時間,會繼續完善這個項目,代碼僅供參考。非常希望有小伙伴與我一起該貢獻
總結
-
parcel優點
- 簡單、簡單、簡單 (重要的事三遍)
- 零配置
-
parcel缺點
- 不能使用alias路徑別名
- 不能按照自己意愿構建資源文件目錄,都打包到目標的根目錄
- 會自動加載很多依賴包,在開發時這個不是很影響,對于新手也不影響
- 對于大項目來說,自定義需求不太容易實現,但是webpack可實現
- 遇到問題只能 github issue, 瀏覽源碼
-
簡單結論
- 代碼思想非常好,純JS的面向對象編寫,可以學習源碼
- 適用于前端新手或非前端人員
- 適合于新框架、新編譯語言的學習
- 適用于簡單前端項目
- 使用于二次開發,個性化的打包工具
番外篇【源碼解讀,待更新...】
引用博文及項目鏈接
本文僅是自己個人觀點,如果紕漏與錯誤,歡迎評論指正