froxy 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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