美文网首页Android开发学习
打包神器之Parcel使用指南

打包神器之Parcel使用指南

作者: 小明小明长大了 | 来源:发表于2018-06-27 13:49 被阅读0次

    简介

    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 开始,可以不用引入一个配置文件。配置内容相当齐全,性能高,也集成了各种个样的插件。基本满足了前端开发绝大部分需求。缺点就是配置相当的多,不适合新手使用。
    • roadhog
      是webpack上层封装,本质上是简化了一系列webpack的配置,生成最终的wepack配置文件,是antd-pro的官方的cli工具。它效果与parcel很像,适用范围窄,打包效率低
    • create-react-app
      集成了react开发的一系列资源,还有命令行工具,对于创建的新的react项目非常好,但是限制也是相当明显的。只适用react

    快速使用

    1. 下载全局依赖
    npm install -g parcel-bundler
    
    1. 新建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;
    }
    
    1. 本地开发
    parcel serve index.html
    
    1. 打包应用
    parcel build index.html 
    

    命令行选项介绍

    1. parcel主要命令
    parcel -h # 查看帮助信息
    parcel serve -h # 本地开发模式,会启动一个默认1234端口的本地服务器,代理到配置的目标文件,前端页面开发为主
    parcel watch -h # 监听文件改动模式,不会启动默认本地服务器,服务端开发为主
    parcel build -h # 编译源文件并打包到目标�文件夹
    
    1. 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

    1. 编写src/app.vue
    <template lang="html">
        <div id="app">
          <h1>Hello Parcel vue app 📦 🚀</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>
    
    1. 编写src/main.js
    import Vue from 'vue'
    import App from './app.vue'
    
    new Vue({
      el: '#app',
      render: h => h(App)
    });
    
    1. 编写index.html
    <html>
    <head>
        <title>Welcome to Vue</title>
    </head>
    <body>
        <div id="app"></div>
        <script src="src/main.js"></script>
    </body>
    </html>
    
    1. 运行与打包
    # 运行
    parcel serve pathto/index.html --no-cache
    # 打包
    parcel build pathto/index.html --public-url . --no-source-maps --no-cache --detailed-report
    

    react-typescript demo

    1. 编写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>
            )
        }
    }
    
    1. 编写index.tsx入口
    import * as React from 'react'
    import { render } from 'react-dom'
    
    import App from './components/App'
    
    render(<App />, document.getElementById('root'))
    
    1. 编写index.html
    <html lang="en">
    <head>
      <title>Parcel with Typescript</title>
    </head>
    <body>
      <div id="root"></div>
      <script src="./index.tsx"></script>
    </body>
    </html>
    
    1. 开发与打包
    parcel serve pathto/index.html --no-cache
    # 打包
    parcel build pathto/index.html --public-url . --no-source-maps --no-cache --detailed-report
    

    多页面应用

    1. 建立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>
    
    1. 建立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;
    }
    
    1. 建立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');
    
    1. 开发与打包 注意这里使用 * 号匹配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 插件

    1. 写一个 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;
    
    1. 写一个 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;
    
    1. 编写插件方法myPlugin.js
    module.exports = function (bundler) {
        bundler.addAssetType('.josn2', require.resolve('./MyAsset'));
        bundler.addPackager('json2', require.resolve('./MyPackager'));
    };
    
    1. 发布到npm中
      将这个包发不到npm时,需要加parcel-plugin-前缀,然后它就会被parcel自动识别。
    2. 使用插件两种方式
    • 只需要将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的面向对象编写,可以学习源码
      • 适用于前端新手或非前端人员
      • 适合于新框架、新编译语言的学习
      • 适用于简单前端项目
      • 使用于二次开发,个性化的打包工具

    番外篇【源码解读,待更新...】


    引用博文及项目链接

    本文仅是自己个人观点,如果纰漏与错误,欢迎评论指正

    相关文章

      网友评论

        本文标题:打包神器之Parcel使用指南

        本文链接:https://www.haomeiwen.com/subject/nbdkeftx.html