主要有以下特点:
- 快速打包: 利用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 开始,可以不用引入一个配置文件。配置内容相当齐全,性能高,也集成了各种个样的插件。基本满足了前端开发绝大部分需求。缺点就是配置相当的多,不适合新手使用。 - roadhog
是webpack上层封装,本质上是简化了一系列webpack的配置,生成最终的wepack配置文件,是antd-pro的官方的cli工具。它效果与parcel很像,适用范围窄,打包效率低。 - create-react-app
集成了react开发的一系列资源,还有命令行工具,对于创建的新的react项目非常好,但是限制也是相当明显的。只适用react
快速使用
-
下载全局依赖
npm install -g parcel-bundler
-
新建html,css,js文件
// index.html
hello world
// 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 # 本地服务器启动端口 --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 # 1 日志输入级别 0 | 1 | 2 | 3 对应 不输出日志 | 仅错误日志 | 警告与错误日志 | 所有日志
-
build 命令
-d, --out-dir # 打包目标文件夹 默认 "dist" -o, --out-file # 项目入口文件的文件名,默认与 --public-url 一致 --no-minify # 打包时不压缩源文件 --detailed-report # 打印详细打包资源报告
主要使用场景实例--- 所有demo无需手动写依赖与配置
由于parcel可以自动加载 vue,react...等框架的依赖包,所以不需要自己特地去将依赖加到package.json中,直接写vue,react组件即可。
vue demo
-
编写
#app { display: flex; justify-content: center; align-items: center; height: 100%; } h1 { font-weight: 300; }src/app.vue -
编写
src/main.jsimport Vue from 'vue' import App from './app.vue'
new Vue({ el: '#app', render: h => h(App) });
-
编写
Welcome to Vueindex.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 (
) } }typescript react component
-
编写
index.tsx入口import * as React from 'react' import { render } from 'react-dom'
import App from './components/App'
render(, document.getElementById('root'))
-
编写
Parcel with Typescriptindex.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
index这是首页
// page1.html
Page 1第一页
返回首页// page2.html
Page 2第二页
返回首页// page3.html
Page 3第三页
返回首页 -
建立
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.jsconst 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.jsconst {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.jsmodule.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的面向对象编写,可以学习源码
- 适用于前端新手或非前端人员
- 适合于新框架、新编译语言的学习
- 适用于简单前端项目
- 使用于二次开发,个性化的打包工具
番外篇【源码解读,待更新...】
引用博文及项目链接
链接:www.jianshu.com/p/42f98e043…
来源:简书