Skip to content

Commit

Permalink
feat: add rollup plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed Jan 28, 2019
1 parent 8efbd9c commit 6a749d3
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 123 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ yarn add import-http --dev

## Usage

### Webpack

In your `webpack.config.js`:

```js
const ImportHttpPlugin = require('import-http')
const ImportHttpWebpackPlugin = require('import-http/webpack')

module.exports = {
plugins: [new ImportHttpPlugin()]
plugins: [new ImportHttpWebpackPlugin()]
}
```

Expand All @@ -52,16 +54,26 @@ console.log(React, Vue)

Run webpack and it just works.

### Rollup

In your `rollup.config.js`:

```js
export default {
plugins: [require('import-http/rollup')()]
}
```

## Caching

Resources will be fetched at the very first build, then the response will be cached in `~/.import-http` dir. You can use the `reload` option to invalidate cache:

```js
const ImportHttpPlugin = require('import-http')
const ImportHttpWebpackPlugin = require('import-http/webpack')

module.exports = {
plugins: [
new ImportHttpPlugin({
new ImportHttpWebpackPlugin({
reload: process.env.RELOAD
})
]
Expand Down
2 changes: 1 addition & 1 deletion example/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const ImportHttpPlugin = require('..')
const ImportHttpPlugin = require('../webpack')

module.exports = {
mode: 'development',
Expand Down
4 changes: 4 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const os = require('os')
const path = require('path')

exports.CACHE_DIR = path.join(os.homedir(), '.import-http')
11 changes: 11 additions & 0 deletions lib/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const fetch = require('node-fetch')

module.exports = async (url, opts) => {
console.log(`Downloading ${url}...`)
const res = await fetch(url, opts)
if (!res.ok) {
console.error(`Failed to download ${url}...`)
throw new Error(res.statusText)
}
return res
}
121 changes: 6 additions & 115 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,117 +1,8 @@
const os = require('os')
const path = require('path')
const { parse: parseURL, resolve: resolveURL } = require('url')
const fetch = require('node-fetch')
const builtinModules = require('builtin-modules')
const { pathExists, outputFile } = require('./utils')
const createHttpCache = require('./http-cache')

const RESOLVER_ID = 'import-http-resolver'

const HTTP_RE = /^https?:\/\//

class ImportHttpPlugin {
constructor(options = {}) {
this.reload = options.reload
this.cacheDir = options.cacheDir
? path.resolve(options.cacheDir)
: path.join(os.homedir(), '.import-http')
this.depsDir = path.join(this.cacheDir, 'deps')
this.httpCache = createHttpCache(path.join(this.cacheDir, 'requests.json'))
// Map<file, moduleId>
this.fileModuleCache = new Map()
}

getFilePathFromURL(url) {
const { host, pathname, protocol } = parseURL(url)
// Where should this url be in the disk
return path.join(this.depsDir, protocol.replace(':', ''), host, pathname)
}

apply(compiler) {
compiler.resolverFactory.hooks.resolver
.for('normal')
.tap(RESOLVER_ID, resolver => {
const resolvedHook = resolver.ensureHook(`resolve`)

resolver
.getHook(`described-resolve`)
.tapAsync(
RESOLVER_ID,
async (requestContext, resolveContext, callback) => {
let id = requestContext.request
const { issuer } = requestContext.context

// if the issuer is a URL (cached in deps dir)
// resolve the url
if (
!HTTP_RE.test(id) &&
issuer &&
this.fileModuleCache.has(issuer) &&
!this.fileModuleCache.has(id)
) {
if (/^\./.test(id)) {
// Relative file
// Excluded something like ./node_modules/webpack/buildin/global.js
if (!/node_modules/.test(id)) {
id = resolveURL(this.fileModuleCache.get(issuer), id)
}
} else if (!builtinModules.includes(id)) {
// Propably an npm package
id = `https://unpkg.com/${id}`
}
}

if (!HTTP_RE.test(id)) {
return callback()
}

const canResolve = (request, url) => {
this.fileModuleCache.set(request, url)
resolver.doResolve(
resolvedHook,
Object.assign({}, requestContext, {
request
}),
null,
resolveContext,
callback
)
}

try {
// Let's get the actual URL
const url = await this.httpCache.get(id)
let file

if (url && !this.reload) {
file = this.getFilePathFromURL(url)
if (await pathExists(file)) {
return canResolve(file, url)
}
}

// We have never requested this before
console.log(`Downloading ${id}...`)
const res = await fetch(id)
if (!res.ok) {
console.error(`Failed to download ${id}...`)
throw new Error(res.statusText)
}
file = this.getFilePathFromURL(res.url)
await this.httpCache.set(id, res.url)
const content = await res
.buffer()
.then(buff => buff.toString('utf8'))
await outputFile(file, content, 'utf8')
canResolve(file, res.url)
} catch (error) {
callback(error)
}
}
)
})
module.exports = {
get WebpackPlugin() {
return require('./webpack')
},
get rollupPlugin() {
return require('./rollup')
}
}

module.exports = ImportHttpPlugin
77 changes: 77 additions & 0 deletions lib/rollup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const path = require('path')
const { parse: parseURL } = require('url')
const {
outputFile,
pathExists,
readFile,
HTTP_RE,
resolveURL
} = require('./utils')
const createHttpCache = require('./http-cache')
const { CACHE_DIR } = require('./constants')
const fetch = require('./fetch')

const PREFIX = '\0'

const removePrefix = id => id && id.replace(/^\0/, '')

const shouldLoad = id => {
return HTTP_RE.test(removePrefix(id))
}

module.exports = ({ reload, cacheDir = CACHE_DIR } = {}) => {
const DEPS_DIR = path.join(cacheDir, 'deps')
const HTTP_CACHE_FILE = path.join(cacheDir, 'requests.json')

const getFilePathFromURL = url => {
const { host, pathname, protocol } = parseURL(url)
// Where should this url be in the disk
return path.join(DEPS_DIR, protocol.replace(':', ''), host, pathname)
}

const httpCache = createHttpCache(HTTP_CACHE_FILE)

return {
name: 'http',

async resolveId(importee, importer) {
// We're importing from URL
if (HTTP_RE.test(importee)) {
return PREFIX + importee
}

// We're importing a file but the importer the a URL
// Then we should do
if (importer) {
importer = importer.replace(/^\0/, '')
if (HTTP_RE.test(importer)) {
return resolveURL(importer, importee)
}
}
},

async load(id) {
if (!shouldLoad(id)) return

id = removePrefix(id)
const url = await httpCache.get(id)
let file

if (url && !reload) {
file = getFilePathFromURL(url)
if (await pathExists(file)) {
return readFile(file, 'utf8')
}
}

// We have never requested this before
const res = await fetch(url)
file = getFilePathFromURL(res.url)
await httpCache.set(id, res.url)
const content = await res.buffer().then(buff => buff.toString('utf8'))
await outputFile(file, content, 'utf8')

return content
}
}
}
18 changes: 18 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const path = require('path')
const util = require('util')
const fs = require('fs')
const url = require('url')
const builtinModules = require('builtin-modules')

const mkdir = util.promisify(require('mkdirp'))

Expand All @@ -18,3 +20,19 @@ exports.pathExists = fp =>
resolve(!err)
})
})

exports.HTTP_RE = /^https?:\/\//

exports.resolveURL = (issuer, id) => {
if (/^\./.test(id)) {
// Relative file
// Excluded something like ./node_modules/webpack/buildin/global.js
if (!/node_modules/.test(id)) {
id = url.resolve(issuer, id)
}
} else if (!builtinModules.includes(id)) {
// Propably an npm package
id = `https://unpkg.com/${id}`
}
return id
}
Loading

0 comments on commit 6a749d3

Please sign in to comment.