bridgetown-core 1.0.0 → 1.1.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -1
  3. data/bridgetown-core.gemspec +0 -1
  4. data/lib/bridgetown-core/collection.rb +39 -22
  5. data/lib/bridgetown-core/commands/apply.rb +3 -3
  6. data/lib/bridgetown-core/commands/build.rb +6 -6
  7. data/lib/bridgetown-core/commands/concerns/actions.rb +3 -2
  8. data/lib/bridgetown-core/commands/concerns/build_options.rb +2 -2
  9. data/lib/bridgetown-core/commands/configure.rb +1 -1
  10. data/lib/bridgetown-core/commands/console.rb +5 -5
  11. data/lib/bridgetown-core/commands/doctor.rb +7 -7
  12. data/lib/bridgetown-core/commands/esbuild/esbuild.defaults.js.erb +95 -12
  13. data/lib/bridgetown-core/commands/esbuild/migrate-from-webpack.rb +1 -6
  14. data/lib/bridgetown-core/commands/new.rb +20 -19
  15. data/lib/bridgetown-core/commands/plugins.rb +47 -9
  16. data/lib/bridgetown-core/commands/registrations.rb +2 -3
  17. data/lib/bridgetown-core/commands/serve.rb +2 -2
  18. data/lib/bridgetown-core/commands/start.rb +3 -0
  19. data/lib/bridgetown-core/commands/webpack/update.rb +3 -3
  20. data/lib/bridgetown-core/commands/webpack/webpack.defaults.js.erb +19 -14
  21. data/lib/bridgetown-core/component.rb +14 -8
  22. data/lib/bridgetown-core/concerns/localizable.rb +20 -0
  23. data/lib/bridgetown-core/concerns/prioritizable.rb +44 -0
  24. data/lib/bridgetown-core/concerns/publishable.rb +11 -1
  25. data/lib/bridgetown-core/concerns/site/configurable.rb +2 -10
  26. data/lib/bridgetown-core/concerns/site/localizable.rb +5 -1
  27. data/lib/bridgetown-core/concerns/site/ssr.rb +3 -3
  28. data/lib/bridgetown-core/concerns/site/writable.rb +28 -0
  29. data/lib/bridgetown-core/concerns/transformable.rb +2 -2
  30. data/lib/bridgetown-core/configuration.rb +4 -2
  31. data/lib/bridgetown-core/configurations/bt-postcss/postcss.config.js +5 -3
  32. data/lib/bridgetown-core/configurations/bt-postcss.rb +1 -1
  33. data/lib/bridgetown-core/configurations/lit/esbuild-plugins.js +21 -0
  34. data/lib/bridgetown-core/configurations/lit/happy-days.lit.js +26 -0
  35. data/lib/bridgetown-core/configurations/lit/lit-components-entry.js +1 -0
  36. data/lib/bridgetown-core/configurations/lit/lit-ssr.config.js +6 -0
  37. data/lib/bridgetown-core/configurations/lit.rb +95 -0
  38. data/lib/bridgetown-core/configurations/open-props/variables.css.erb +11 -0
  39. data/lib/bridgetown-core/configurations/open-props.rb +21 -0
  40. data/lib/bridgetown-core/configurations/ruby2js/hello_world.js.rb +9 -0
  41. data/lib/bridgetown-core/configurations/ruby2js.rb +67 -0
  42. data/lib/bridgetown-core/configurations/shoelace.rb +50 -0
  43. data/lib/bridgetown-core/configurations/tailwindcss.rb +16 -2
  44. data/lib/bridgetown-core/configurations/turbo/turbo_transitions.js +1 -1
  45. data/lib/bridgetown-core/converters/erb_templates.rb +7 -2
  46. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +1 -1
  47. data/lib/bridgetown-core/converters/serbea_templates.rb +1 -4
  48. data/lib/bridgetown-core/drops/generated_page_drop.rb +2 -1
  49. data/lib/bridgetown-core/drops/resource_drop.rb +2 -1
  50. data/lib/bridgetown-core/errors.rb +5 -5
  51. data/lib/bridgetown-core/filters/translation_filters.rb +11 -0
  52. data/lib/bridgetown-core/filters/url_filters.rb +37 -10
  53. data/lib/bridgetown-core/filters.rb +3 -0
  54. data/lib/bridgetown-core/frontmatter_defaults.rb +14 -8
  55. data/lib/bridgetown-core/generated_page.rb +1 -0
  56. data/lib/bridgetown-core/kramdown/parser/gfm.rb +36 -0
  57. data/lib/bridgetown-core/model/base.rb +3 -4
  58. data/lib/bridgetown-core/plugin.rb +6 -37
  59. data/lib/bridgetown-core/plugin_manager.rb +3 -2
  60. data/lib/bridgetown-core/rack/boot.rb +7 -2
  61. data/lib/bridgetown-core/rack/logger.rb +14 -4
  62. data/lib/bridgetown-core/rack/roda.rb +106 -9
  63. data/lib/bridgetown-core/rack/routes.rb +67 -2
  64. data/lib/bridgetown-core/resource/base.rb +9 -6
  65. data/lib/bridgetown-core/resource/destination.rb +18 -0
  66. data/lib/bridgetown-core/resource/permalink_processor.rb +6 -4
  67. data/lib/bridgetown-core/resource/relations.rb +1 -1
  68. data/lib/bridgetown-core/ruby_template_view.rb +3 -3
  69. data/lib/bridgetown-core/static_file.rb +1 -1
  70. data/lib/bridgetown-core/tags/highlight.rb +1 -1
  71. data/lib/bridgetown-core/tags/post_url.rb +1 -1
  72. data/lib/bridgetown-core/url.rb +1 -1
  73. data/lib/bridgetown-core/utils/aux.rb +2 -1
  74. data/lib/bridgetown-core/utils/require_gems.rb +3 -6
  75. data/lib/bridgetown-core/utils.rb +24 -11
  76. data/lib/bridgetown-core/version.rb +2 -2
  77. data/lib/bridgetown-core/watcher.rb +21 -8
  78. data/lib/bridgetown-core.rb +8 -2
  79. data/lib/site_template/Gemfile.erb +4 -0
  80. data/lib/site_template/README.md +2 -2
  81. data/lib/site_template/bridgetown.config.yml +3 -0
  82. data/lib/site_template/frontend/javascript/index.js.erb +1 -0
  83. data/lib/site_template/frontend/styles/syntax-highlighting.css +77 -0
  84. data/lib/site_template/package.json.erb +18 -18
  85. data/lib/site_template/server/roda_app.rb +3 -6
  86. data/lib/site_template/src/404.html +2 -1
  87. data/lib/site_template/src/500.html +10 -0
  88. metadata +20 -19
  89. data/lib/bridgetown-core/publisher.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdb62333635d80ceb9561a0cd9be68f0220ea729c8ac4505bb8e4460894075e1
4
- data.tar.gz: f7377127b4d64846e694d8ba4c032706ed161ea1595cf8dbf3eca3d2b6e4692d
3
+ metadata.gz: afc674e5aa40d66a2b9e4f24c0aad39108f47e70c8d4756cbfa86623ed2dc7e0
4
+ data.tar.gz: 4bf462c506c3fcdd09a4750bedae19cb2ee4ff429b57a6266dd56d62645d4926
5
5
  SHA512:
6
- metadata.gz: 051a34337053c1370745d37a7cc3def36e1623193e4c03e0b1006d06280303ad5442c289465185314f916dbcf2aa1b1123990ca196041b03bf3804a03b36f695
7
- data.tar.gz: d5adcc3f8c7c7938bbc220329c002c7cea281f4e21f8ea2bab685dabaffe48c5acca3998482d1e0ec6d0c7c172751c0c6b34481821f51b5d1ccc687b137e55a5
6
+ metadata.gz: 5baaa3f133d0e1cbd5e7e972645f107361088d9d11b5bebb38374573d898b67a07b318b4d19e1e5731a696dafe88a3b2ded6ba1c068b869e5bad750200905417
7
+ data.tar.gz: 4a88c972c98f54ac8b98538fec81f907797cb423f6fa89907cab21a58c29d5961b5e6bf156a3d2846a6d2fd831656696fa9a2afd76d2c2a6f3f5b62663db2c8d
data/.rubocop.yml CHANGED
@@ -11,12 +11,17 @@ AllCops:
11
11
  - vendor/**/*
12
12
  - tmp/**/*
13
13
  - test/source/**/*
14
- - test/resources/src/_pages/*.rb
14
+ - test/resources/src/**/*.rb
15
+ - lib/bridgetown-core/commands/base.rb
16
+ - lib/bridgetown-core/commands/plugins.rb
17
+ - lib/bridgetown-core/configurations/ruby2js/**/*
18
+ - lib/bridgetown-core/rack/roda.rb
15
19
  - lib/site_template/TEMPLATES/**/*
16
20
  - lib/site_template/Rakefile
17
21
  - lib/site_template/config.ru
18
22
  - lib/site_template/config/**/*
19
23
  - lib/site_template/plugins/site_builder.rb
24
+ - lib/site_template/server/roda_app.rb
20
25
 
21
26
  Lint/ConstantDefinitionInBlock:
22
27
  Exclude:
@@ -44,7 +44,6 @@ Gem::Specification.new do |s|
44
44
  s.add_runtime_dependency("kramdown-parser-gfm", "~> 1.0")
45
45
  s.add_runtime_dependency("liquid", "~> 5.0")
46
46
  s.add_runtime_dependency("listen", "~> 3.0")
47
- s.add_runtime_dependency("rack-indifferent", ">= 1.2.0")
48
47
  s.add_runtime_dependency("rake", ">= 13.0")
49
48
  s.add_runtime_dependency("roda", "~> 3.46")
50
49
  s.add_runtime_dependency("rouge", "~> 3.0")
@@ -20,11 +20,11 @@ module Bridgetown
20
20
  end
21
21
 
22
22
  def builtin?
23
- label.in? %w(posts pages data).freeze
23
+ @is_builtin ||= label.in?(%w(posts pages data).freeze)
24
24
  end
25
25
 
26
26
  def data?
27
- label == "data"
27
+ @is_data ||= label == "data"
28
28
  end
29
29
 
30
30
  # Fetch the Resources in this collection.
@@ -215,7 +215,7 @@ module Bridgetown
215
215
  # Used by Resource's permalink processor
216
216
  # @return [String]
217
217
  def default_permalink
218
- metadata.fetch("permalink", "/:collection/:path/")
218
+ metadata.fetch("permalink", "/:locale/:collection/:path/")
219
219
  end
220
220
 
221
221
  # Extract options for this collection from the site configuration.
@@ -270,32 +270,29 @@ module Bridgetown
270
270
 
271
271
  # Read in resource from repo path
272
272
  # @param full_path [String]
273
- def read_resource(full_path, manifest: nil) # rubocop:todo Metrics/AbcSize
274
- scheme = manifest ? "plugin" : "repo"
275
- id = +"#{scheme}://#{label}.collection/"
276
- id += "#{manifest.origin}/" if manifest
277
- id += Addressable::URI.escape(
278
- Pathname(full_path).relative_path_from(
279
- manifest ? Pathname(manifest.content) : Pathname(site.source)
280
- ).to_s
281
- ).gsub("#", "%23")
282
- model = Bridgetown::Model::Base.find(id)
283
-
284
- if model.attributes.key?(:locale) && model.locale.to_sym == :multi
273
+ def read_resource(full_path, manifest: nil)
274
+ model_relative_path = relative_model_path_for(full_path, manifest: manifest)
275
+ model = Bridgetown::Model::Base.find(model_id_from_relative_path(model_relative_path,
276
+ manifest: manifest))
277
+
278
+ if model_is_multi_locale?(model, model_relative_path)
285
279
  site.config.available_locales.each do |locale|
286
280
  model.locale = locale
287
- add_model_resource model
281
+ add_resource_from_model model
288
282
  end
289
283
  return
290
284
  end
291
285
 
292
- add_model_resource model
286
+ add_resource_from_model model
293
287
  end
294
288
 
295
- def add_model_resource(model)
296
- resource = model.to_resource.read!
297
- resources << resource if site.config.unpublished || resource.published?
289
+ # @param model [Bridgetown::Model::Base]
290
+ def add_resource_from_model(model)
291
+ model.to_resource.read!.tap do |resource|
292
+ resources << resource if resource.publishable?
293
+ end
298
294
  end
295
+ alias_method :add_model_resource, :add_resource_from_model
299
296
 
300
297
  def sort_resources!
301
298
  if metadata["sort_by"].is_a?(String)
@@ -346,8 +343,8 @@ module Bridgetown
346
343
  end
347
344
 
348
345
  def order_with_warning(sort_key, resource, order)
349
- Bridgetown.logger.warn "Sort warning:", "'#{sort_key}' not defined in" \
350
- " #{resource.relative_path}"
346
+ Bridgetown.logger.warn "Sort warning:", "'#{sort_key}' not defined in " \
347
+ "#{resource.relative_path}"
351
348
  order
352
349
  end
353
350
 
@@ -365,5 +362,25 @@ module Bridgetown
365
362
  self
366
363
  )
367
364
  end
365
+
366
+ def relative_model_path_for(full_path, manifest: nil)
367
+ Pathname(full_path).relative_path_from(
368
+ manifest ? Pathname(manifest.content) : Pathname(site.source)
369
+ ).to_s
370
+ end
371
+
372
+ def model_id_from_relative_path(model_relative_path, manifest: nil)
373
+ scheme = manifest ? "plugin" : "repo"
374
+ id = +"#{scheme}://#{label}.collection/"
375
+ id += "#{manifest.origin}/" if manifest
376
+ id += Addressable::URI.escape(model_relative_path).gsub("#", "%23")
377
+ id
378
+ end
379
+
380
+ def model_is_multi_locale?(model, model_relative_path)
381
+ (model.attributes.key?(:locale) && model.locale.to_sym == :multi) ||
382
+ File.extname(File.basename(model_relative_path, ".*")) == ".multi" ||
383
+ site.frontmatter_defaults.all(model_relative_path, label.to_sym)["locale"].to_s == "multi"
384
+ end
368
385
  end
369
386
  end
@@ -58,9 +58,9 @@ module Bridgetown
58
58
  automation_command = args.empty? ? "bridgetown.automation.rb" : args[0]
59
59
 
60
60
  if args.empty? && !File.exist?("bridgetown.automation.rb")
61
- raise ArgumentError, "You must specify a path or a URL," \
62
- " or add bridgetown.automation.rb to the" \
63
- " current folder."
61
+ raise ArgumentError, "You must specify a path or a URL, " \
62
+ "or add bridgetown.automation.rb to the " \
63
+ "current folder."
64
64
  end
65
65
 
66
66
  Bridgetown.with_unbundled_env do
@@ -22,8 +22,8 @@ module Bridgetown
22
22
  desc: "Watch for changes and rebuild"
23
23
 
24
24
  def self.print_startup_message
25
- Bridgetown.logger.info "Starting:", "Bridgetown v#{Bridgetown::VERSION.magenta}" \
26
- " (codename \"#{Bridgetown::CODE_NAME.yellow}\")"
25
+ Bridgetown.logger.info "Starting:", "Bridgetown v#{Bridgetown::VERSION.magenta} " \
26
+ "(codename \"#{Bridgetown::CODE_NAME.yellow}\")"
27
27
  end
28
28
 
29
29
  # Build your bridgetown site
@@ -45,8 +45,8 @@ module Bridgetown
45
45
  @site = Bridgetown::Site.new(config_options)
46
46
 
47
47
  if config_options.fetch("skip_initial_build", false)
48
- Bridgetown.logger.warn "Build Warning:", "Skipping the initial build." \
49
- " This may result in an out-of-date site."
48
+ Bridgetown.logger.warn "Build Warning:", "Skipping the initial build. " \
49
+ "This may result in an out-of-date site."
50
50
  else
51
51
  build_site(config_options)
52
52
  end
@@ -77,8 +77,8 @@ module Bridgetown
77
77
  end
78
78
  Bridgetown.logger.info "Generating…"
79
79
  @site.process
80
- Bridgetown.logger.info "Done! 🎉", "#{"Completed".bold.green} in less than" \
81
- " #{(Time.now - t).ceil(2)} seconds."
80
+ Bridgetown.logger.info "Done! 🎉", "#{"Completed".bold.green} in less than " \
81
+ "#{(Time.now - t).ceil(2)} seconds."
82
82
 
83
83
  return unless config_options[:using_puma]
84
84
 
@@ -54,7 +54,8 @@ module Bridgetown
54
54
 
55
55
  def add_bridgetown_plugin(gemname, version: nil)
56
56
  version = " -v \"#{version}\"" if version
57
- run "bundle add #{gemname}#{version} -g bridgetown_plugins"
57
+ run "bundle add #{gemname}#{version} -g bridgetown_plugins",
58
+ env: { "BUNDLE_GEMFILE" => File.join(destination_root, "Gemfile") }
58
59
  rescue SystemExit
59
60
  say_status :run, "Gem not added due to bundler error", :red
60
61
  end
@@ -79,7 +80,7 @@ module Bridgetown
79
80
 
80
81
  def determine_remote_filename(arg)
81
82
  if arg.end_with?(".rb")
82
- arg.split("/").yield_self do |segments|
83
+ arg.split("/").then do |segments|
83
84
  arg.sub!(%r!/#{segments.last}$!, "")
84
85
  segments.last
85
86
  end
@@ -21,8 +21,8 @@ module Bridgetown
21
21
  desc: "Destination directory (defaults to output)"
22
22
  klass.class_option :root_dir,
23
23
  aliases: "-r",
24
- desc: "The top-level root folder" \
25
- " where config files are located"
24
+ desc: "The top-level root folder " \
25
+ "where config files are located"
26
26
  klass.class_option :plugins_dir,
27
27
  aliases: "-p",
28
28
  type: :array,
@@ -60,7 +60,7 @@ module Bridgetown
60
60
 
61
61
  def configurations
62
62
  inside self.class.source_root do
63
- return Dir.glob("*.rb").map { |file| file.sub(".rb", "") }
63
+ return Dir.glob("*.rb").map { |file| file.sub(".rb", "") }.sort
64
64
  end
65
65
  end
66
66
 
@@ -64,9 +64,9 @@ module Bridgetown
64
64
  require "irb/ext/save-history"
65
65
  require "amazing_print" unless options[:"bypass-ap"]
66
66
 
67
- Bridgetown.logger.info "Starting:", "Bridgetown v#{Bridgetown::VERSION.magenta}" \
68
- " (codename \"#{Bridgetown::CODE_NAME.yellow}\")" \
69
- " console…"
67
+ Bridgetown.logger.info "Starting:", "Bridgetown v#{Bridgetown::VERSION.magenta} " \
68
+ "(codename \"#{Bridgetown::CODE_NAME.yellow}\") " \
69
+ "console…"
70
70
  Bridgetown.logger.info "Environment:", Bridgetown.environment.cyan
71
71
  site = Bridgetown::Site.new(configuration_with_overrides(options))
72
72
 
@@ -80,8 +80,8 @@ module Bridgetown
80
80
  IRB.conf[:MAIN_CONTEXT] = irb.context
81
81
  Bridgetown.logger.info "Console:", "Your site is now available as #{"site".cyan}"
82
82
  Bridgetown.logger.info "",
83
- "You can also access #{"collections".cyan} or perform a" \
84
- " #{"reload!".cyan}"
83
+ "You can also access #{"collections".cyan} or perform a " \
84
+ "#{"reload!".cyan}"
85
85
 
86
86
  trap("SIGINT") do
87
87
  irb.signal_handle
@@ -62,8 +62,8 @@ module Bridgetown
62
62
  next unless paths.size > 1
63
63
 
64
64
  conflicting_urls = true
65
- Bridgetown.logger.warn "Conflict:", "The URL '#{url}' is the destination" \
66
- " for the following pages: #{paths.join(", ")}"
65
+ Bridgetown.logger.warn "Conflict:", "The URL '#{url}' is the destination " \
66
+ "for the following pages: #{paths.join(", ")}"
67
67
  end
68
68
  conflicting_urls
69
69
  end
@@ -77,8 +77,8 @@ module Bridgetown
77
77
  urls_only_differ_by_case = true
78
78
  Bridgetown.logger.warn(
79
79
  "Warning:",
80
- "The following URLs only differ by case. On a case-insensitive file system one of the" \
81
- " URLs will be overwritten by the other: #{real_urls.join(", ")}"
80
+ "The following URLs only differ by case. On a case-insensitive file system one of " \
81
+ "the URLs will be overwritten by the other: #{real_urls.join(", ")}"
82
82
  )
83
83
  end
84
84
  urls_only_differ_by_case
@@ -121,7 +121,7 @@ module Bridgetown
121
121
  def url_exists?(url)
122
122
  return true unless url.nil? || url.empty?
123
123
 
124
- Bridgetown.logger.warn "Warning:", "You didn't set an URL in the config file, "\
124
+ Bridgetown.logger.warn "Warning:", "You didn't set an URL in the config file, " \
125
125
  "you may encounter problems with some plugins."
126
126
  false
127
127
  end
@@ -132,7 +132,7 @@ module Bridgetown
132
132
  # Addressable::URI#parse only raises a TypeError
133
133
  # https://git.io/vFfbx
134
134
  rescue TypeError
135
- Bridgetown.logger.warn "Warning:", "The site URL does not seem to be valid, "\
135
+ Bridgetown.logger.warn "Warning:", "The site URL does not seem to be valid, " \
136
136
  "check the value of `url` in your config file."
137
137
  false
138
138
  end
@@ -140,7 +140,7 @@ module Bridgetown
140
140
  def url_absolute(url)
141
141
  return true if url.is_a?(String) && Addressable::URI.parse(url).absolute?
142
142
 
143
- Bridgetown.logger.warn "Warning:", "Your site URL does not seem to be absolute, "\
143
+ Bridgetown.logger.warn "Warning:", "Your site URL does not seem to be absolute, " \
144
144
  "check the value of `url` in your config file."
145
145
  false
146
146
  end
@@ -11,11 +11,33 @@
11
11
  const path = require("path")
12
12
  const fsLib = require("fs")
13
13
  const fs = fsLib.promises
14
+ const { pathToFileURL, fileURLToPath } = require("url")
14
15
  const glob = require("glob")
15
16
  const postcss = require("postcss")
16
17
  const postCssImport = require("postcss-import")
17
18
  const readCache = require("read-cache")
18
19
 
20
+ // Detect if an NPM package is available
21
+ const moduleAvailable = name => {
22
+ try {
23
+ require.resolve(name)
24
+ return true
25
+ } catch (e) { }
26
+ return false
27
+ }
28
+
29
+ // Generate a Source Map URL (used by the Sass plugin)
30
+ const generateSourceMappingURL = sourceMap => {
31
+ const data = Buffer.from(JSON.stringify(sourceMap), "utf-8").toString("base64")
32
+ return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${data} */`
33
+ }
34
+
35
+ // Import Sass if available
36
+ let sass
37
+ if (moduleAvailable("sass")) {
38
+ sass = require("sass")
39
+ }
40
+
19
41
  // Glob plugin derived from:
20
42
  // https://github.com/thomaschaaf/esbuild-plugin-import-glob
21
43
  // https://github.com/xiaohui-zhangxh/jsbundling-rails/commit/b15025dcc20f664b2b0eb238915991afdbc7cb58
@@ -61,24 +83,23 @@ const importGlobPlugin = () => ({
61
83
  },
62
84
  })
63
85
 
64
- const postCssPlugin = (options) => ({
86
+ // Plugin for PostCSS
87
+ const postCssPlugin = (options, configuration) => ({
65
88
  name: "postcss",
66
89
  async setup(build) {
67
90
  // Process .css files with PostCSS
68
- build.onLoad({ filter: /\.(css)$/ }, async (args) => {
91
+ build.onLoad({ filter: (configuration.filter || /\.css$/) }, async (args) => {
69
92
  const additionalFilePaths = []
70
93
  const css = await fs.readFile(args.path, "utf8")
71
94
 
72
95
  // Configure import plugin so PostCSS can properly resolve `@import`ed CSS files
73
96
  const importPlugin = postCssImport({
74
- filter: itemPath => {
75
- // We'll want to track any imports later when in watch mode
76
- additionalFilePaths.push(path.resolve(path.dirname(args.path), itemPath))
77
- return true
78
- },
97
+ filter: itemPath => !itemPath.startsWith("/"), // ensure it doesn't try to import source-relative paths
79
98
  load: async filename => {
80
99
  let contents = await readCache(filename, "utf-8")
81
100
  const filedir = path.dirname(filename)
101
+ // We'll want to track any imports later when in watch mode:
102
+ additionalFilePaths.push(filename)
82
103
 
83
104
  // We need to transform `url(...)` in imported CSS so the filepaths are properly
84
105
  // relative to the entrypoint. Seems icky to have to hack this! C'est la vie...
@@ -106,6 +127,65 @@ const postCssPlugin = (options) => ({
106
127
  },
107
128
  })
108
129
 
130
+ // Plugin for Sass
131
+ const sassPlugin = (options) => ({
132
+ name: "sass",
133
+ async setup(build) {
134
+ // Process .scss and .sass files with Sass
135
+ build.onLoad({ filter: /\.(sass|scss)$/ }, async (args) => {
136
+ if (!sass) {
137
+ console.error("error: Sass is not installed. Try running `yarn add sass` and then building again.")
138
+ return
139
+ }
140
+
141
+ const modulesFolder = pathToFileURL("node_modules/")
142
+
143
+ const localOptions = {
144
+ importers: [{
145
+ // An importer that redirects relative URLs starting with "~" to
146
+ // `node_modules`.
147
+ findFileUrl(url) {
148
+ if (!url.startsWith('~')) return null
149
+ return new URL(url.substring(1), modulesFolder)
150
+ }
151
+ }],
152
+ sourceMap: true,
153
+ ...options
154
+ }
155
+ const result = sass.compile(args.path, localOptions)
156
+
157
+ const watchPaths = result.loadedUrls
158
+ .filter((x) => x.protocol === "file:" && !x.pathname.startsWith(modulesFolder.pathname))
159
+ .map((x) => x.pathname)
160
+
161
+ let cssOutput = result.css.toString()
162
+
163
+ if (result.sourceMap) {
164
+ const basedir = process.cwd()
165
+ const sourceMap = result.sourceMap
166
+
167
+ const promises = sourceMap.sources.map(async source => {
168
+ const sourceFile = await fs.readFile(fileURLToPath(source), "utf8")
169
+ return sourceFile
170
+ })
171
+ sourceMap.sourcesContent = await Promise.all(promises)
172
+
173
+ sourceMap.sources = sourceMap.sources.map(source => {
174
+ return path.relative(basedir, fileURLToPath(source))
175
+ })
176
+
177
+ cssOutput += '\n' + generateSourceMappingURL(sourceMap)
178
+ }
179
+
180
+ return {
181
+ contents: cssOutput,
182
+ loader: "css",
183
+ watchFiles: [args.path, ...watchPaths],
184
+ }
185
+ })
186
+ },
187
+ })
188
+
109
189
  // Set up defaults and generate frontend bundling manifest file
110
190
  const bridgetownPreset = (outputFolder) => ({
111
191
  name: "bridgetownPreset",
@@ -151,9 +231,9 @@ const bridgetownPreset = (outputFolder) => ({
151
231
  // We have an entrypoint!
152
232
  manifest[stripPrefix(value.entryPoint)] = outputPath
153
233
  entrypoints.push([outputPath, fileSize(key)])
154
- } else if (key.match(/index(\.js)?\.[^-.]*\.css/) && inputs.find(item => item.endsWith("index.css"))) {
234
+ } else if (key.match(/index(\.js)?\.[^-.]*\.css/) && inputs.find(item => item.match(/\.(s?css|sass)$/))) {
155
235
  // Special treatment for index.css
156
- manifest[stripPrefix(inputs.find(item => item.endsWith("index.css")))] = outputPath
236
+ manifest[stripPrefix(inputs.find(item => item.match(/\.(s?css|sass)$/)))] = outputPath
157
237
  entrypoints.push([outputPath, fileSize(key)])
158
238
  } else if (inputs.length > 0) {
159
239
  // Naive implementation, we'll just grab the first input and hope it's accurate
@@ -182,9 +262,12 @@ const postCssConfig = postcssrc.sync()
182
262
  module.exports = (outputFolder, esbuildOptions) => {
183
263
  esbuildOptions.plugins = esbuildOptions.plugins || []
184
264
  // Add the PostCSS & glob plugins to the top of the plugin stack
185
- esbuildOptions.plugins.unshift(postCssPlugin(postCssConfig))
265
+ esbuildOptions.plugins.unshift(postCssPlugin(postCssConfig, esbuildOptions.postCssPluginConfig || {}))
266
+ if (esbuildOptions.postCssPluginConfig) delete esbuildOptions.postCssPluginConfig
186
267
  esbuildOptions.plugins.unshift(importGlobPlugin())
187
- // Add the Bridgetown preset to the bottom of the plugin stack
268
+ // Add the Sass plugin
269
+ esbuildOptions.plugins.push(sassPlugin(esbuildOptions.sassOptions || {}))
270
+ // Add the Bridgetown preset
188
271
  esbuildOptions.plugins.push(bridgetownPreset(outputFolder))
189
272
 
190
273
  // esbuild, take it away!
@@ -200,7 +283,7 @@ module.exports = (outputFolder, esbuildOptions) => {
200
283
  ".ttf": "file",
201
284
  ".eot": "file",
202
285
  },
203
- resolveExtensions: [".tsx",".ts",".jsx",".js",".css",".json",".js.rb"],
286
+ resolveExtensions: [".tsx", ".ts", ".jsx", ".js", ".css", ".scss", ".sass", ".json", ".js.rb"],
204
287
  nodePaths: ["frontend/javascript", "frontend/styles"],
205
288
  watch: process.argv.includes("--watch"),
206
289
  minify: process.argv.includes("--minify"),
@@ -2,11 +2,6 @@
2
2
 
3
3
  # rubocop:disable Layout/LineLength
4
4
 
5
- if package_json["devDependencies"].key?("sass")
6
- say "Unable to migrate, project uses Sass. Please migrate to PostCSS first before migrating to esbuild."
7
- return
8
- end
9
-
10
5
  remove_file "webpack.config.js"
11
6
  remove_file "config/webpack.defaults.js"
12
7
 
@@ -16,7 +11,7 @@ default_postcss_config = File.expand_path("../../../site_template/postcss.config
16
11
  template default_postcss_config, "postcss.config.js"
17
12
 
18
13
  unless Bridgetown.environment.test?
19
- required_packages = %w(esbuild glob postcss postcss-flexbugs-fixes postcss-preset-env postcss-import postcss-load-config)
14
+ required_packages = %w(esbuild glob postcss postcss-flexbugs-fixes postcss-preset-env postcss-import postcss-load-config@3.1.4)
20
15
  redundant_packages = %w(esbuild-loader webpack webpack-cli webpack-manifest-plugin webpack-merge css-loader file-loader mini-css-extract-plugin postcss-loader)
21
16
 
22
17
  say "Installing required packages"
@@ -43,7 +43,7 @@ module Bridgetown
43
43
  desc: "Skip 'yarn install'"
44
44
  class_option :"use-sass",
45
45
  type: :boolean,
46
- desc: "(Webpack only) Create a Sass configuration instead of using PostCSS"
46
+ desc: "Set up a Sass configuration for your stylesheet"
47
47
 
48
48
  DOCSURL = "https://bridgetownrb.com/docs"
49
49
 
@@ -62,19 +62,14 @@ module Bridgetown
62
62
  def new_site
63
63
  raise ArgumentError, "You must specify a path." if args.empty?
64
64
 
65
- if frontend_bundling_option != "webpack" && options["use-sass"]
66
- raise ArgumentError,
67
- "To install Sass, you must choose Webpack (-e webpack) as your frontend bundler"
68
- end
69
-
70
65
  new_site_path = File.expand_path(args.join(" "), Dir.pwd)
71
66
  @site_name = new_site_path.split(File::SEPARATOR).last
72
67
 
73
68
  if preserve_source_location?(new_site_path, options)
74
69
  say_status :conflict, "#{new_site_path} exists and is not empty.", :red
75
70
  Bridgetown.logger.abort_with(
76
- "Ensure #{new_site_path} is empty or else try again with `--force` to proceed and" \
77
- " overwrite any files."
71
+ "Ensure #{new_site_path} is empty or else try again with `--force` to proceed and " \
72
+ "overwrite any files."
78
73
  )
79
74
  end
80
75
 
@@ -98,7 +93,11 @@ module Bridgetown
98
93
  end
99
94
 
100
95
  def postcss_option
101
- !(frontend_bundling_option == "webpack" && options["use-sass"])
96
+ !options["use-sass"]
97
+ end
98
+
99
+ def disable_postcss?
100
+ options["use-sass"] && options["frontend-bundling"] == "webpack"
102
101
  end
103
102
 
104
103
  def create_site(new_site_path)
@@ -116,6 +115,7 @@ module Bridgetown
116
115
  template("frontend/javascript/index.js.erb", "frontend/javascript/index.js")
117
116
  template("src/index.md.erb", "src/index.md")
118
117
  template("src/posts.md.erb", "src/posts.md")
118
+ copy_file("frontend/styles/syntax-highlighting.css")
119
119
 
120
120
  case options["templates"]
121
121
  when "erb"
@@ -126,11 +126,11 @@ module Bridgetown
126
126
  setup_liquid_templates
127
127
  end
128
128
 
129
+ postcss_option ? configure_postcss : configure_sass
130
+
129
131
  if frontend_bundling_option == "esbuild"
130
- configure_postcss
131
132
  invoke(Esbuild, ["setup"], {})
132
133
  else
133
- postcss_option ? configure_postcss : configure_sass
134
134
  invoke(Webpack, ["setup"], {})
135
135
  end
136
136
  end
@@ -165,6 +165,7 @@ module Bridgetown
165
165
  end
166
166
 
167
167
  def configure_sass
168
+ template("postcss.config.js.erb", "postcss.config.js") unless disable_postcss?
168
169
  copy_file("frontend/styles/index.css", "frontend/styles/index.scss")
169
170
  end
170
171
 
@@ -193,18 +194,18 @@ module Bridgetown
193
194
  logger = Bridgetown.logger
194
195
  bt_start = "bin/bridgetown start"
195
196
  logger.info ""
196
- logger.info "Success!".green, "🎉 Your new Bridgetown site was" \
197
- " generated in #{cli_path.cyan}."
197
+ logger.info "Success!".green, "🎉 Your new Bridgetown site was " \
198
+ "generated in #{cli_path.cyan}."
198
199
  if options["skip-yarn"]
199
200
  logger.info "You can now #{"cd".cyan} #{cli_path.cyan} to get started."
200
- logger.info "You'll probably also want to #{"yarn install".cyan}" \
201
- " to load in your frontend assets."
201
+ logger.info "You'll probably also want to #{"yarn install".cyan} " \
202
+ "to load in your frontend assets."
202
203
  else
203
- logger.info "You can now #{"cd".cyan} #{cli_path.cyan} and run #{bt_start.cyan}" \
204
- " to get started."
204
+ logger.info "You can now #{"cd".cyan} #{cli_path.cyan} and run #{bt_start.cyan} " \
205
+ "to get started."
205
206
  end
206
- logger.info "Then check out our online documentation for" \
207
- " next steps: #{DOCSURL.cyan}"
207
+ logger.info "Then check out our online documentation for " \
208
+ "next steps: #{DOCSURL.cyan}"
208
209
 
209
210
  if @skipped_bundle
210
211
  logger.info "Bundle install skipped.".yellow