GithubHelp home page GithubHelp logo

porterhq / porter Goto Github PK

View Code? Open in Web Editor NEW
41.0 9.0 7.0 1.53 MB

🚛 A middleware for browser module authoring

Home Page: https://porterhq.github.io/porter/zh/

JavaScript 45.57% HTML 0.13% Shell 0.11% TypeScript 46.11% Rust 8.09%
loader middleware javascript module-loader module-bundler commonjs es2015

porter's Introduction

NPM Downloads NPM Version codecov

中文版

Porter is a bundler which makes web applications with hybrid module formats easier to compile the assets and mitigate browser compatibility nuances.

User Guides

It is recommended to first start with our starter documentation or the thorough user guides.

Packages

Porter consists of two major packages, @cara/porter the middleware and @cara/porter-cli the command line interface. These are the two packages we publish to NPM.

Packages for demo or test purposes now resides in the examples directory. For users interested in porter-cli,

  • examples/component may be referenced as a demo of using porter-cli to develop a browser module.
  • examples/cli may be referenced as a demo of using porter-cli to develop a web application.

As of examples/app, users interested in porter the middleware may take the app.js in examples/app for example. Many options of porter the middleware, and edge cases of browser modules in NPM, are tested in examples/app. Pardon if you find the code within examples/app a bit messy.

How to Contribute

To learn more about the project setup of Porter, please read our contributing guides.

porter's People

Contributors

chenhui5416 avatar corey600 avatar cyjake avatar dependabot[bot] avatar jserme avatar kfitfk avatar lb4027221 avatar retrythat avatar shaozj avatar yisibl avatar yunlei23 avatar zimond avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

porter's Issues

Cyclic dependencies between components and modules

In a typical web application development, one might want to refactor some of the components as modules to better sharing them between apps. In rare cases, the refactored modules want to require some component of the app. Whether or not this is anti-pattern is not the issue I want to discuss about here. This issue only talks about the changes needed to allow this kind of usage.

For example, components may require('@app/common'), @app/common may require util of the comonents:

app
├── components
│   └── util
└── node_modules
    └── @app
        └── common

module specifiers with unicode literals

require('./中文.js'); // works
require('./\u4E2D\u6587.js'); // does not work

the latter form generates requests like /\u4E2D\u6587.js currently, which needs to be converted back to 中文.js before resolving it.

worker-loader support?

Possible changes:

  • document and window shall not be referenced.
  • dependencies shall be loaded with importScripts().

Syntaxes practiced by Webpack's worker-loader currently:

import Worker from 'worker-loader!./Worker.js';
const worker = new Worker();

Or with module rules configured:

import Worker from './file.worker.js'
const worker = new Worker();

As a matter of fact, this is no different than create worker with vanilla Web Worker syntax:

const worker = new Worker('file.worker.js')
// or if file.worker.js is a ES6 module or CommonJS module that requires Porter to intervene
const worker = new Worker('file.worker.js?main')

With ?main appended, while serving file.worker.js:

  • the loader gets prepended,
  • the dependencies introduced by require gets prefetched with importScripts()

Regarding the syntax introduced by worker-loader,

require('worker-loader!./Worker.js')
// is actually translated into
function WorkerLoader(dep) {  // dep => './Worker.js'
  const uri = require.resolve(dep)
  return new Worker(uri)
}

Whether or not the options of worker-loader shall be supported is still under consideration.

Test cases about `require(//uri)`

Currently this is possible,

// in component code
require('https://a1.alicdn.com/assets/qrcode.js')

But this is not covered in tests yet, needs work. Also, currently there can be no return values since the id passed to require is the full uri but modules are all registered by id in oceanify.registry.

Case sensitivity on components' name

Normally, macOS formats the disk with a case insensitive file system, which makes components' resolutions a bit wild since both require('fooBar') and require('foobar') work. But when the file is compiled and deployed onto a Linux box, the default file system is case sensitive, which makes either require('fooBar') or require('foobar') fail.

😕

Component paths should be specific

Currently all three paths can access home.js:

/porter-app/1.0.0/home.js
/porter-app/home.js
/home.js

However, /porter-app/home.js shouldn't be available since it causes chaos in following scenario:

// home.js
require('../foo')

When loader resolves ../foo above, the context is porter-app/1.0.0/home. The resolved module id will be porter-app/foo, which is accessible in development (because porter middleware handles that), but not available in production due to there's no ../foo actually.

I think the middleware should only allow two types of paths:

/porter-app/1.0.0/home.js
/home.js

The latter one is for main scripts, <script src="/home.js"></script> is much easier to write. After home.js loads, and the loader kicks in, all components and modules loaded later will have their versions specified.

An alias structure that supports arbitrary levels of dependencies

{
  "bluebird": {
    "2.3.11": {
      "main": "./js/main/bluebird.js"
    }
  },
  "ez-editor": {
    "0.2.4": {
      "dependencies": {
        "crox": "1.3.1",
        "extend-object": "1.0.0",
        "heredoc": "1.3.1",
        "inherits": "2.0.1",
        "jquery": "1.11.3",
        "yen": "1.2.4"
      }
    }
  },
  "heredoc": {
    "1.2.0": {},
    "1.3.1": {}
  },
  "yen": {
    "1.2.4": {}
  }
}

The idea is to flat all module versions into a two level object. Detailed descriptions like main and dependencies are all put under the two level path, such as alias.heredoc['1.2.0'].

Test cases about includeModules

In single page applications, there's few entries components. Conventionally, engineers wants to bundle everything into one script to optimize the network performance. In ad industry, the situation is quite similar since the html banner is a (poor man's) single page application. In cases like these, we can use oceanify.compileComponent(id, { includeModules: true }) to bundle them all.

Currently this functionality has been used in our products for a long time, but the test cases in this repo has yet to cover. This issue serves as a reminder.

Conditional Compile

Since 5.1.0-beta, oceanify has introduced module transformation with babel. To some webpack users, use case like below might be familiar:

if (process.env.BROWSER) {
  require('./browser')
} else {
  require('./node')
}

Currently oceanify has not support this however. In Oceanify both ./browser and ./node will be parsed as module dependencies which will eventually fail when loaded in the browser. To fix this, we can add an extra transformation with babel, with process.env.BROWSER positive, and drop the dead branch.

There's a babel plugin already. I'll see to it whenever possible.

Cannot `require('../index')` in the test cases of heredoc

heredoc is a module that supports both browser and node. In its repo, both the source code of the module and the test cases are shared between these two environments.

In node, we can of course require('../index') in test/ folder to grab the heredoc module. In browser however, with current possible scenario setup by oceanify@4, we cannot require a module that is out of the test/ folder. In the other hand, we must require('heredoc').

This makes code sharing a little fuzzy.


Here's one possible fix. We can add a opts.self option to indicate that the source of current module (all the .js files within current working directory) shall be available. Then with opts.self turned on, if any module within components require some dependencies beyond components' scope, we'll change it to the mapped require.

Take the heredoc example. require('../index') will be checked to see if path.resolve(cwd, 'test', '../index') + .js exists or not. If positive, the require statement shall be changed to require('heredoc/index'), then the oceanify middleware can take over the rest.

Only compile module entries that are actually used

Currently the compileAll processes following stuff,

  • node_modules required in components by name, compile by main and bundle by module.
  • components main entries, bundle required components.
  • all components, without bundling.

Recently we've been trying react (a bit old-fashioned, i know), which fancy requiring lib modules by full id such as fbjs/lib/warning.js. [email protected] manages to support this, but the implementation is quite limited yet.

If this behavior is the favored one, we might need to add a tree scanning logic and do all the node_modules by actual entries other than the main in package.json.

preparations for v2 release

Porter is drastically refactored to make following enhancements and new features be possible:

  • Instant module resolution.
  • Bundling by package on the fly.
  • Automatic bundling (${name}/${version}/~bundle.js) on packages that have multiple entries, such as lodash and fbjs.
  • Support require("worker-loader!foo) to migrate existing code.
  • Proper require.resolve to new Worker(require.resolve('foo')), which is basically the same as above.
  • Full support on browser field #1
  • TypeScript support.

Syntax for CSS modules

两种使用场景:

  1. CSS 模块之间的依赖
  2. JS 模块依赖 CSS 模块(module),组成一个组件(component)

后者比较容易达成一致,直接 require('./some.css') 就可以了。是否支持 https://github.com/css-modules/css-modules 再议。

前者是本文讨论重点,目前比较纠结的地方有:

  1. 入口代码的写法
  2. 引用(声明)依赖的方式

针对入口代码,有两种方式:

!= linkTag('/app.css')
link(rel='stylesheet', href='/app.css')

这里假设 app.css 代码如下:

/*
 *= require('./base')
 *= require('./iconfont')
 *= require('./layout')
 */

/* styles */

前者效仿 Rails 里的 Sprockets,在开发模式下实际渲染的时候,会展开为:

<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="iconfont.css">
<link rel="stylesheet" href="layout.css">
<link rel="stylesheet" href="app.css">

在上线前,则通过 rake assets:precompile 任务来压缩、合并样式文件,从而兼得开发时的便利和上线时的性能优化。

后者,link(rel='stylesheet', href='/app.css') 这种用法,则等同于直接写 <link> 标签,由 Oceanify 在响应 app.css 请求的时候提供合并好的 app.css

不过,也不是必须这么做,如果我们采用 @import 作为引用依赖的方式,那么在开发时,我们就可以不合并样式,靠原生的 @import 即可。也就是说 app.css 可以写成这样:

@import './base.css'
@import './iconfont.css'
@import './layout.css'

/* styles */

在上线合并的时候,交给 oceanify.compileAll() 处理掉这些 @imports

所以大家不妨投票,选择入口代码写法:

  1. != linkTag
  2. <link> 直接写

以及模块声明方式:

  1. /*= require('./base') */
  2. @import './base.css'

我倾向选择 2, 2

importmaps and import.meta.resolve()

Safari has it now WebKit/WebKit@6c1996a , Chrome and Edge has it for a while already, the default bundling behavior should be upgraded a bit to better utilize browser capability.

about import.meta.resolve(), currently porter only handles import.meta.url and import.meta.glob() (non-standard), the transformation of it should be refactored to handle following usages:

  • import.meta.resolve(specifier, base)
  • import.meta

import.meta.resolve() looks like the require.resolve() supported by porter though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.