bridgetown-core 1.0.0 → 1.1.0.beta3

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.
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