froxy 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea2d91815589713a9c355fe514e3a47c3d7fb3b3d2e3d896749363ea172c9f5f
4
- data.tar.gz: 63b66d8cc02a6257ccb98fb750732d555b6b0044bfd38a867c70b13aa4ee4cab
3
+ metadata.gz: 926d7b56d53ab4dc548a73b3d8f06c231d0bff8a3ea6ade601b6b4625582c529
4
+ data.tar.gz: 50e66435e3858795c88af5f20609e286336fa627e15f2f430ea66a617faf719f
5
5
  SHA512:
6
- metadata.gz: 744903b02aea11cc67710d81b5798c6a269db6784b0790a4ee7386f9b20b53869ef84c3e64ba3d097af1775c91d07380d08cf31516cc2869ed5eae41f4292a4a
7
- data.tar.gz: 8144a07ff8f146585e3e6fc71fed1da287ea155590158dc8e2dc9519a06d61708e115fc9dde7e0f135a18f3960638d126acb2dfae9b95ca2a06985b0db75781d
6
+ metadata.gz: 65623c88771f9a1654d2612fa31954adc8f35ee7152575e8c9b14aef146a77acb63715b2f4ae37ee807ec13faea7ad02a3a6c9941ea06b7cfb39268e5d7e3990
7
+ data.tar.gz: f2a78301922e4dd6fbf505dc3c0a83bf94a7a6878bfc2c8d7109b0111e813367bc6425fd661b3bd1ad9b927188aeaf5c1f5130010ddcb2631a0827aaac7cbfdb
data/.gitignore CHANGED
@@ -6,5 +6,6 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
- /test/internal/public
9
+ /test/internal/public/
10
+ /test/internal/tmp/
10
11
  /node_modules/
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- froxy (0.1.1)
4
+ froxy (0.1.2)
5
5
  activesupport (>= 5.0.0, < 7.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -24,6 +24,7 @@ production mode will likely include pre-built cachable assets.
24
24
  - Code Splitting.
25
25
  - Source Maps.
26
26
  - Minification.
27
+ - PostCSS support.
27
28
 
28
29
  ## Roadmap
29
30
 
@@ -32,7 +33,6 @@ In no particular order:
32
33
  - Pre-bundling / cached assets.
33
34
  - Typescript.
34
35
  - CSS Modules.
35
- - PostCSS support.
36
36
 
37
37
  ## Installation
38
38
 
@@ -82,6 +82,12 @@ imported from JS will result in the requested CSS injected into an HTML `link` t
82
82
  import '/my/styles.css'
83
83
  ```
84
84
 
85
+ ### PostCSS
86
+
87
+ [PostCSS](https://postcss.org/) is supported out of the box. If any
88
+ [PostCSS config](https://github.com/postcss/postcss-load-config) is found in your project, then any
89
+ local CSS (not from node modules) will be parsed with PostCSS.
90
+
85
91
  ### Images/Fonts, etc.
86
92
 
87
93
  When called directly, images are served directly - avoiding a call to esbuild. But when an image is
@@ -153,10 +159,12 @@ There are a few options that you can customise, and they are all defined in your
153
159
  example:
154
160
 
155
161
  ```json
156
- "froxy": {
157
- "target": [],
158
- "aliases": {
159
- "_": "lodash"
162
+ {
163
+ "froxy": {
164
+ "target": [],
165
+ "aliases": {
166
+ "_": "lodash"
167
+ }
160
168
  }
161
169
  }
162
170
  ```
@@ -185,6 +193,22 @@ See esbuild's documentation on [minification](https://esbuild.github.io/api/#min
185
193
 
186
194
  See esbuild's documentation on [sourcemap](https://esbuild.github.io/api/#sourcemap).
187
195
 
196
+ #### `ignore`
197
+
198
+ A regular expression matching paths that should be ignored and not built or bundled.
199
+
200
+ Example:
201
+
202
+ Ignore all paths starting with "/fonts/":
203
+
204
+ ```json
205
+ {
206
+ "froxy": {
207
+ "ignore": "^\\/fonts\\/"
208
+ }
209
+ }
210
+ ```
211
+
188
212
  ## Development
189
213
 
190
214
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/bin/froxy CHANGED
@@ -1,27 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const esbuild = require(require.resolve('esbuild', { paths: [process.cwd()] }))
3
+ const [, , cwd, entryPoint] = process.argv
4
4
 
5
- const [, , absWorkingDir, entryPoint] = process.argv
5
+ const esbuild = require(require.resolve('esbuild', { paths: [cwd] }))
6
6
 
7
7
  const { resolve, config } = require('../lib/froxy/esbuild/utils')
8
8
  const loadStylePlugin = require('../lib/froxy/esbuild/plugins/load_style')
9
- const envPlugin = require('../lib/froxy/esbuild/plugins/env')
10
- const aliasPlugin = require('../lib/froxy/esbuild/plugins/alias')(absWorkingDir)
11
- const cssPlugin = require('../lib/froxy/esbuild/plugins/css')(absWorkingDir)
12
- const imagesPlugin = require('../lib/froxy/esbuild/plugins/images')(absWorkingDir)
13
- const rootPlugin = require('../lib/froxy/esbuild/plugins/root')(absWorkingDir)
14
-
15
- const conf = config(absWorkingDir)
9
+ const ignorePlugin = require('../lib/froxy/esbuild/plugins/ignore')
10
+ const aliasPlugin = require('../lib/froxy/esbuild/plugins/alias')
11
+ const cssPlugin = require('../lib/froxy/esbuild/plugins/css')
12
+ const imagesPlugin = require('../lib/froxy/esbuild/plugins/images')
13
+ const rootPlugin = require('../lib/froxy/esbuild/plugins/root')
16
14
 
17
15
  const buildOptions = {
18
- absWorkingDir,
16
+ absWorkingDir: cwd,
19
17
  entryPoints: [entryPoint],
20
18
  bundle: true,
21
- target: conf.target,
22
- minify: conf.minify,
23
- inject: conf.inject,
24
- sourcemap: conf.sourcemap,
19
+ target: config.target,
20
+ minify: config.minify,
21
+ inject: config.inject,
22
+ sourcemap: config.sourcemap,
25
23
  format: 'esm',
26
24
  splitting: true,
27
25
  outdir: 'public/froxy/build',
@@ -32,9 +30,11 @@ const buildOptions = {
32
30
  'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`,
33
31
  'process.env.RAILS_ENV': `"${process.env.RAILS_ENV || 'development'}"`
34
32
  },
35
- plugins: [aliasPlugin, envPlugin, loadStylePlugin, cssPlugin, imagesPlugin, rootPlugin]
33
+ plugins: [aliasPlugin, loadStylePlugin, cssPlugin, imagesPlugin, rootPlugin]
36
34
  }
37
35
 
36
+ config.ignore && buildOptions.plugins.unshift(ignorePlugin)
37
+
38
38
  esbuild.build(buildOptions).catch(() => {
39
39
  process.exit(1)
40
40
  })
@@ -17,14 +17,14 @@ const { resolve, config } = require('../utils')
17
17
  // import { map } from '_'
18
18
  // import axios from 'myaxios'
19
19
  //
20
- module.exports = absWorkingDir => ({
20
+ module.exports = {
21
21
  name: 'froxy.alias',
22
22
  setup(build) {
23
23
  let map = []
24
24
  let aliases = {}
25
25
 
26
26
  try {
27
- map = config(absWorkingDir).aliases
27
+ map = config.aliases
28
28
  aliases = Object.keys(map)
29
29
  } catch {
30
30
  // Fail silently, as there is no package.json.
@@ -35,11 +35,11 @@ module.exports = absWorkingDir => ({
35
35
  const re = new RegExp(`^${aliases.map(x => escapeRegExp(x)).join('|')}$`)
36
36
 
37
37
  build.onResolve({ filter: re }, async ({ resolveDir, path }) => ({
38
- path: await resolve(absWorkingDir, resolveDir, map[path], { fallbackToEsbuild: true })
38
+ path: await resolve(resolveDir, map[path], { fallbackToEsbuild: true })
39
39
  }))
40
40
  }
41
41
  }
42
- })
42
+ }
43
43
 
44
44
  function escapeRegExp(string) {
45
45
  // $& means the whole matched string
@@ -3,21 +3,24 @@ const fs = require('fs')
3
3
 
4
4
  const { resolve } = require('../utils')
5
5
 
6
- module.exports = absWorkingDir => ({
6
+ const [, , cwd] = process.argv
7
+
8
+ module.exports = {
7
9
  name: 'froxy.css',
8
10
  setup(build) {
9
- // Handle CSS imported from JS.
10
- build.onResolve({ filter: /\.css$/ }, args => {
11
- let resolvedPath = resolve(absWorkingDir, args.resolveDir, args.path)
11
+ build.onResolve({ filter: /\.css$/ }, async args => {
12
+ let resolvedPath = resolve(args.resolveDir, args.path)
12
13
 
14
+ // Resolved path is not relative or absolute, so we assume it's a node module, and attempt to
15
+ // resolve it from node_modules.
13
16
  if (resolvedPath === args.path) {
14
- const nodeModulesDir = path.join(absWorkingDir, 'node_modules')
17
+ const nodeModulesDir = path.join(cwd, 'node_modules')
15
18
 
16
19
  try {
17
- fs.accessSync(nodeModulesDir, fs.constants.R_OK)
20
+ await fs.promises.access(nodeModulesDir, fs.constants.R_OK)
18
21
  resolvedPath = path.join(nodeModulesDir, args.path)
19
22
  } catch {
20
- // Do nothing
23
+ // Fail siently
21
24
  }
22
25
  }
23
26
 
@@ -27,24 +30,46 @@ module.exports = absWorkingDir => ({
27
30
  }
28
31
  })
29
32
 
30
- // Handles CSS imports from JS (eg `import from 'some.css'`) by simply marking it as external.
31
- // This then allows the browser to handle the import. However, browsers do not yet support
32
- // importing non-JS assets, and will not include the CSS. So the Froxy proxy will return the
33
- // imported CSS as a JS file that inserts the CSS directly into the DOM. This unfortunately may
34
- // result in a flash of unstyled content (FOUC).
35
- //
36
- // --- OR
37
- //
38
- // esbuild returns the content of both the JS and CSS. Then Froxy returns the JS as normal,
39
- // and additionally includes the CSS directly into the rendered HTML. This way, there will be no
40
- // FOUC. But this method is a little more complex, as Froxy will need to somehow pass the CSS
41
- // content to Rails for insertion into the rendered view.
42
- build.onLoad({ filter: /\.css$/, namespace: 'cssFromJs' }, args => ({
43
- contents: `
44
- import loadStyle from 'loadStyle'
45
- loadStyle("${args.path.slice(absWorkingDir.length)}")
46
- `,
47
- loader: 'js'
48
- }))
33
+ // When CSS is requested directly - and not imported from JS.
34
+ build.onLoad({ filter: /\.css$/, namespace: 'file' }, async args => {
35
+ // Don't parse node modules with postcss.
36
+ if (args.path.includes('node_modules')) return
37
+
38
+ const postcssrc = require(require.resolve('postcss-load-config', { paths: [cwd] }))
39
+
40
+ let postcssConfig
41
+ try {
42
+ postcssConfig = await postcssrc({ cwd: cwd }, cwd)
43
+ } catch {
44
+ // Fail siently
45
+ }
46
+
47
+ if (postcssConfig) {
48
+ const postcss = require(require.resolve('postcss', { paths: [cwd] }))
49
+ const css = await fs.promises.readFile(args.path, 'utf8')
50
+
51
+ const result = await postcss(postcssConfig.plugins).process(css, {
52
+ ...postcssConfig.options,
53
+ from: args.path
54
+ })
55
+
56
+ return {
57
+ contents: result.css,
58
+ loader: 'css'
59
+ }
60
+ }
61
+ })
62
+
63
+ // When CSS is imported from JS, the CSS is injected into the DOM as a <link> tag. The browser
64
+ // then loads the CSS file as it usually would.
65
+ build.onLoad({ filter: /\.css$/, namespace: 'cssFromJs' }, args => {
66
+ return {
67
+ contents: `
68
+ import loadStyle from 'loadStyle'
69
+ loadStyle("${args.path.slice(cwd.length)}")
70
+ `,
71
+ loader: 'js'
72
+ }
73
+ })
49
74
  }
50
- })
75
+ }
@@ -0,0 +1,14 @@
1
+ const path = require('path')
2
+
3
+ const { config } = require('../utils')
4
+
5
+ if (!config.ignore) return
6
+
7
+ module.exports = {
8
+ name: 'froxy.ignore',
9
+ setup(build) {
10
+ build.onResolve({ filter: new RegExp(config.ignore) }, args => ({
11
+ external: true
12
+ }))
13
+ }
14
+ }
@@ -1,6 +1,8 @@
1
1
  const { resolve } = require('../utils')
2
2
 
3
- module.exports = absWorkingDir => ({
3
+ const [, , cwd] = process.argv
4
+
5
+ module.exports = {
4
6
  name: 'froxy.images',
5
7
  setup(build) {
6
8
  const IMAGE_TYPES = /\.(png|gif|jpe?g|svg|ico|webp|avif)$/
@@ -10,11 +12,11 @@ module.exports = absWorkingDir => ({
10
12
  // exported using the default export. Including an image in CSS using `url()`, will simply
11
13
  // return the relative URL of the image.
12
14
  build.onResolve({ filter: IMAGE_TYPES }, args => {
13
- const resolvedPath = resolve(absWorkingDir, args.resolveDir, args.path)
15
+ const resolvedPath = resolve(args.resolveDir, args.path)
14
16
 
15
17
  if (args.importer.endsWith('.css')) {
16
18
  return {
17
- path: resolvedPath.slice(absWorkingDir.length),
19
+ path: resolvedPath.slice(cwd.length),
18
20
  external: true
19
21
  }
20
22
  } else {
@@ -24,9 +26,9 @@ module.exports = absWorkingDir => ({
24
26
 
25
27
  build.onLoad({ filter: IMAGE_TYPES }, args => {
26
28
  return {
27
- contents: `export default '${args.path.slice(absWorkingDir.length)}';`,
29
+ contents: `export default '${args.path.slice(cwd.length)}';`,
28
30
  loader: 'js'
29
31
  }
30
32
  })
31
33
  }
32
- })
34
+ }
@@ -1,6 +1,8 @@
1
1
  const path = require('path')
2
2
 
3
- module.exports = absWorkingDir => ({
3
+ const [, , cwd] = process.argv
4
+
5
+ module.exports = {
4
6
  name: 'froxy.root',
5
7
  setup(build) {
6
8
  // Resolves paths starting with a `/` to the Rails root.
@@ -8,7 +10,7 @@ module.exports = absWorkingDir => ({
8
10
  // Example:
9
11
  // import '/my/lib.js' //-> import '{Rails.root}/my/lib.js'
10
12
  build.onResolve({ filter: /^\// }, args => ({
11
- path: path.join(absWorkingDir, args.path)
13
+ path: path.join(cwd, args.path)
12
14
  }))
13
15
  }
14
- })
16
+ }
@@ -1,39 +1,42 @@
1
1
  const path = require('path')
2
2
  const fs = require('fs')
3
- const esbuild = require(require.resolve('esbuild', { paths: [process.cwd()] }))
4
3
 
5
- // Resolve the given path (`p`) to an absolute path.
6
- const resolve = (absWorkingDir, b, p, options = {}) => {
7
- options = {
8
- fallbackToEsbuild: false,
9
- ...options
10
- }
4
+ const [, , cwd] = process.argv
11
5
 
12
- if (p.startsWith('/')) return path.resolve(absWorkingDir, p.slice(1))
13
- if (p.startsWith('.')) return path.resolve(b, p)
6
+ module.exports = {
7
+ // Resolve the given path (`p`) to an absolute path.
8
+ resolve: (b, p, options = {}) => {
9
+ options = {
10
+ fallbackToEsbuild: false,
11
+ ...options
12
+ }
14
13
 
15
- if (options.fallbackToEsbuild) {
16
- return (async () => await esbuildResolve(p, b))()
17
- }
14
+ if (p.startsWith('/')) return path.resolve(cwd, p.slice(1))
15
+ if (p.startsWith('.')) return path.resolve(b, p)
18
16
 
19
- return p
20
- }
17
+ if (options.fallbackToEsbuild) {
18
+ return (async () => await esbuildResolve(p, b))()
19
+ }
21
20
 
22
- const config = absWorkingDir => {
23
- let packageConfig = {}
24
- const defaultConfig = {
25
- minify: false,
26
- sourcemap: true
27
- }
21
+ return p
22
+ },
28
23
 
29
- try {
30
- const pkg = fs.readFileSync(path.join(absWorkingDir, 'package.json'))
31
- packageConfig = JSON.parse(pkg).froxy
32
- } catch {
33
- // Fail silently, as there is no package.json.
34
- }
24
+ get config() {
25
+ let packageConfig = {}
26
+ const defaultConfig = {
27
+ minify: false,
28
+ sourcemap: true
29
+ }
30
+
31
+ try {
32
+ const pkg = fs.readFileSync(path.join(cwd, 'package.json'))
33
+ packageConfig = JSON.parse(pkg).froxy
34
+ } catch {
35
+ // Fail silently, as there is no package.json.
36
+ }
35
37
 
36
- return { ...defaultConfig, ...packageConfig }
38
+ return { ...defaultConfig, ...packageConfig }
39
+ }
37
40
  }
38
41
 
39
42
  // Resolves a module using esbuild module resolution.
@@ -41,9 +44,12 @@ const config = absWorkingDir => {
41
44
  // @param {string} id Module to resolve
42
45
  // @param {string} [resolveDir] The directory to resolve from
43
46
  // @returns {string} The resolved module
44
- async function esbuildResolve(id, resolveDir = process.cwd()) {
47
+ async function esbuildResolve(id, resolveDir) {
48
+ const esbuild = require(require.resolve('esbuild', { paths: [cwd] }))
49
+
45
50
  let _resolve
46
51
  const resolvedPromise = new Promise(resolve => (_resolve = resolve))
52
+
47
53
  return Promise.race([
48
54
  resolvedPromise,
49
55
  esbuild
@@ -76,8 +82,3 @@ async function esbuildResolve(id, resolveDir = process.cwd()) {
76
82
  .then(() => id)
77
83
  ])
78
84
  }
79
-
80
- module.exports = {
81
- resolve,
82
- config
83
- }
data/lib/froxy/proxy.rb CHANGED
@@ -2,13 +2,16 @@
2
2
 
3
3
  require 'open3'
4
4
  require 'rack/utils'
5
+ require 'active_support/benchmarkable'
5
6
 
6
7
  # Proxies files to esbuild.
7
8
  module Froxy
8
9
  class Proxy
10
+ include ActiveSupport::Benchmarkable
11
+
9
12
  BUILD_PATH = 'public/froxy/build'
10
13
  CLI = File.expand_path('../../bin/froxy', __dir__)
11
- IMAGE_TYPES = /\.(png|gif|jpeg|jpg|svg|ico|webp|avif)$/i.freeze
14
+ FALLTHRU_TYPES = /\.(png|gif|jpeg|jpg|svg|ico|webp|avif)$/i.freeze
12
15
  FILE_EXT_MAP = {
13
16
  '.jsx' => '.js'
14
17
  }.freeze
@@ -25,7 +28,7 @@ module Froxy
25
28
 
26
29
  if req.get? || req.head?
27
30
  # Let images through.
28
- return @file_server.call(env) if IMAGE_TYPES.match?(path_info)
31
+ return @file_server.call(env) if FALLTHRU_TYPES.match?(path_info)
29
32
 
30
33
  # Let JS sourcemaps through.
31
34
  return @build_file_server.call(env) if /\.js\.map$/i.match?(path_info)
@@ -34,9 +37,12 @@ module Froxy
34
37
  if /\.(js|jsx|css)$/i.match?(path_info)
35
38
  return unless (path = clean_path(path_info))
36
39
  return [404, {}, []] unless file_readable?(path)
40
+
37
41
  return @file_server.call(env) unless Rails.application.config.froxy.use_esbuild
38
42
 
39
- return build env, req, path
43
+ return benchmark logging_message(req) do
44
+ build env, req, path
45
+ end
40
46
  end
41
47
  end
42
48
 
@@ -45,6 +51,10 @@ module Froxy
45
51
 
46
52
  private
47
53
 
54
+ def logging_message(request)
55
+ format '[froxy] "%s" for %s at %s', request.path_info, request.ip, Time.now.to_default_s
56
+ end
57
+
48
58
  def path_to_file(env, request, path)
49
59
  ext = Pathname.new(path).extname
50
60
  request.path_info = path.sub(/#{ext}$/, FILE_EXT_MAP[ext]) if FILE_EXT_MAP.key?(ext)
@@ -57,7 +67,6 @@ module Froxy
57
67
  stdout, stderr, status = Open3.capture3(CLI, Rails.root.to_s, path)
58
68
 
59
69
  if status.success?
60
- Rails.logger.info "[froxy] built #{path}"
61
70
  raise "[froxy] build failed: #{stderr}" unless stderr.empty?
62
71
  else
63
72
  non_empty_streams = [stdout, stderr].delete_if(&:empty?)
@@ -79,5 +88,9 @@ module Froxy
79
88
  path = Rack::Utils.unescape_path path_info.chomp('/').delete_prefix('/')
80
89
  Rack::Utils.clean_path_info path if Rack::Utils.valid_path? path
81
90
  end
91
+
92
+ def logger
93
+ Rails.logger
94
+ end
82
95
  end
83
96
  end
data/lib/froxy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Froxy
4
- VERSION = '0.1.2'
4
+ VERSION = '0.2.0'
5
5
  end
data/package.json CHANGED
@@ -8,7 +8,5 @@
8
8
  "singleQuote": true,
9
9
  "arrowParens": "avoid"
10
10
  },
11
- "devDependencies": {
12
- "esbuild": "0.8.50"
13
- }
11
+ "devDependencies": {}
14
12
  }
data/yarn.lock CHANGED
@@ -2,7 +2,3 @@
2
2
  # yarn lockfile v1
3
3
 
4
4
 
5
- esbuild@0.8.50:
6
- version "0.8.50"
7
- resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.50.tgz#ebf24fde0cdad1a369789dd6fd7a820b0a01e46c"
8
- integrity sha512-oidFLXssA7IccYzkqLVZSqNJDwDq8Mh/vqvrW+3fPWM7iUiC5O2bCllhnO8+K9LlyL/2Z6n+WwRJAz9fqSIVRg==
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: froxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-22 00:00:00.000000000 Z
11
+ date: 2021-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -55,6 +55,7 @@ files:
55
55
  - lib/froxy/esbuild/plugins/css.js
56
56
  - lib/froxy/esbuild/plugins/debug.js
57
57
  - lib/froxy/esbuild/plugins/env.js
58
+ - lib/froxy/esbuild/plugins/ignore.js
58
59
  - lib/froxy/esbuild/plugins/images.js
59
60
  - lib/froxy/esbuild/plugins/load_style.js
60
61
  - lib/froxy/esbuild/plugins/root.js