Nanoc 4 upgrade guide
Nanoc 4 takes a clean break from the past, removing anything that was holding back future development.
The good news is that Nanoc 4.0 is similar to 3.8. Upgrading a Nanoc 3.x site to Nanoc 4.0 only takes minutes.
Why upgrade?
- Nanoc 4 brings identifiers with extensions, and thereby solves a long-standing usability issue. It also introduces glob patterns, which makes rules easier to write.
- Nanoc 4 paves the way for new features and performance improvements. Nanoc 3 exposed its internals in a public API, making it hard to make significant changes.
- Nanoc 3 is in maintenance mode, which means it will only get critical bug fixes.
Installing Nanoc 4
Before installing, ensure you have a supported version of Ruby. Nanoc supports Ruby 2.5 and up:
% ruby --version
ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [x86_64-linux]
%
To upgrade Ruby, follow the installation instructions on the Ruby website.
You can install Nanoc 4 using RubyGems:
% gem install nanoc
We recommend using Bundler to manage dependencies. When using Bundler, ensure there is a line for Nanoc in the Gemfile that looks like this:
gem 'nanoc', '~> 4.0'
Quick upgrade guide
The following steps will get a Nanoc 3 site working on Nanoc 4 with a minimal amount of changes.
- Change mentions of
Nanoc3
toNanoc
. - Change mentions of
@site.config
to@config
. - Add
identifier_type: legacy
to the individual data source configurations. For example:data_sources: - type: filesystem
data_sources: - type: filesystem identifier_type: legacy
- Add
string_pattern_type: legacy
to the configuration file. For example:data_sources: - type: filesystem identifier_type: legacy
string_pattern_type: legacy data_sources: - type: filesystem identifier_type: legacy
- In Rules, remove the
rep.
prefix fromfilter
,layout
, andsnapshot
. For example:compile '*' do rep.filter :erb rep.layout 'default' end
compile '*' do filter :erb layout 'default' end
- In the
preprocess
block, use@items.create
rather than instantiatingNanoc::Item
. For example:@items << Nanoc::Item.new('Hello', {}, '/hello/')
@items.create('Hello', {}, '/hello/')
- In data sources, use
#new_item
or#new_layout
rather than instantiatingNanoc::Item
orNanoc::Layout
. For example:def items [Nanoc::Item.new('Hello', {}, '/hello/')] end
def items [new_item('Hello', {}, '/hello/')] end
- In data sources, replace the
identifier
argument in calls to#new_item
and#new_layout
with an explicitNanoc::Identifier
instance, constructed withtype: legacy
:def items [new_item('Hello', {}, '/hello/')] end
def items [new_item('Hello', {}, Nanoc::Identifier.new('/hello/', type: :legacy))] end
- Replace
.reps[0]
by.reps[:default]
. For example:item.reps[0].path
item.reps[:default].path
- Replace calls to
#rep_named
byreps[something]
, where something is the argument to#rep_named
. For example:item.rep_named(:raw).path
item.reps[:raw].path
- If you use the static data source, disable it for now and follow the extended upgrade instructions below.
Extended upgrade guide
This section describes how to upgrade a site to identifiers with extensions and glob patterns. For details, see the Identifiers and patterns page.
This section assumes you have already upgraded the site following the instructions in the Quick upgrade guide section above.
Before you start, add enable_output_diff: true
to the configuration file. This will let the compile command write out a diff with the changes to the compiled output. This diff will allow you to verify that no unexpected changes occur.
html5small
, we recommend turning it off before upgrading the site, so that the output diff becomes easier to read.Enabling glob patterns
Before enabling them, ensure you are familiar with glob patterns. For details, see the Glob patterns section on the Identifiers and patterns page.
To use glob patterns:
- Set
string_pattern_type
toglob
in the configuration file. For example:string_pattern_type: legacy
string_pattern_type: glob
- Ensure that all string patterns in the Rules file, as well as in calls to
@items[…]
,@layouts[…]
, and#render
throughout the site, start and end with a slash. This is an intermediate step. For example:# Before compile 'articles/*' do layout 'default' end
# After compile '/articles/*/' do layout '/default/' end
# Before @items['foo'] @layouts['/bar']
# After @items['/foo/'] @layouts['/bar/']
<!-- Before --> <%= render 'header' %>
<!-- After --> <%= render '/header/' %>
- Replace
*
and+
with**/*
in all string patterns in the Rules file, as well as in calls to@items[…]
,@layouts[…]
, and#render
throughout the site. For example:compile '/articles/*/' do layout '/default/' end
compile '/articles/**/*/' do layout '/default/' end
@items['/articles/*/']
@items['/articles/**/*/']
This approach should work out of the box: Nanoc should not raise errors and the output diff should be empty.
Enabling identifiers with extensions
Before enabling them, ensure you are familiar with identifiers with extensions. See the Identifiers section on the Identifiers and patterns page for documentation.
To use identifiers with extensions:
- Set
identifier_type
tofull
in the configuration file. For example:identifier_type: legacy
identifier_type: full
- Remove the trailing slash from any argument to
#compile
,#route
, and#layout
in the Rules file, as well as in calls to@items[…]
,@layouts[…]
, and#render
throughout the site. If the pattern does not end with a “*
”, add “.*
”. For example:compile '/articles/**/*/' do filter :kramdown layout '/default/' end compile '/about/' do layout '/default/' end
compile '/articles/**/*' do filter :kramdown layout '/default.*' end compile '/about.*' do layout '/default.*' end
@items['/about/'] @layouts['/default/']
@items['/about.*'] @layouts['/default.*']
<%= render '/root/' %>
<%= render '/root.*' %>
- Update the routing rules to output the correct path. For example:
route '/articles/*/' do # /articles/foo/ gets written to /articles/foo/index.html item.identifier + 'index.html' end
route '/articles/**/*' do # /articles/foo.md gets written to /articles/foo/index.html item.identifier.without_ext + '/index.html' end
- Create a routing rule that matches index files in the content directory (such as content/index.md or content/blog/index.md). For example, put the following before any rules matching
/**/*
:route '/**/index.*' do # /projects/index.md gets written to /projects/index.html item.identifier.without_ext + '.html' end
- If the site has calls to
#children
, ensure the lib/helpers.rb file containsuse_helper Nanoc::Helpers::ChildParent
, and replace method calls to#children
with a function call to#children_of
, passing in the item as an argument. For example:@items['/articles/'].children @item.children
children_of(@items['/articles/']) children_of(@item)
- If the site has calls to
#parent
, ensure the lib/helpers.rb file containsuse_helper Nanoc::Helpers::ChildParent
, and replace method calls to#parent
with a function call to#parent_of
, passing in the item as an argument. For example:@item.parent
parent_of(@item)
Upgrading from the static data source
The static data source no longer exists in Nanoc 4. It existed in Nanoc 3 to work around the problem of identifiers not including the file extension, which is no longer the case in Nanoc 4.
Theoretically, with identifiers with extensions enabled, it is possible to move the contents of the static/ directory into content/. This can be tricky, however, because some rules that did not match any items in static/ might now match.
Because of this, the recommended approach for upgrading is to keep the static/ directory, and set up a new data source that reads from this directory.
In the site configuration, re-enable the static data source, change its type to filesystem
, set content_dir
to "static"
and layouts_dir
to null
:
data_sources:
-
type: filesystem
-
type: filesystem
items_root: /static
content_dir: 'static'
layouts_dir: null
The null value for the layouts_dir
option prevents this data source from loading layouts—the other data source already does so.
Lastly, update the rules to copy these items as-is, but without the /static
prefix:
compile '/static/**/*' do
end
route '/static/**/*' do
# /static/foo.html → /foo.html
item.identifier.to_s.sub(/\A\/static/, '')
end
This approach should work out of the box: Nanoc should not raise errors and the output diff should be empty.
A final improvement would be to move the contents of the static/ directory into content/. The main thing to watch out for with this approach is rules that accidentally match the wrong items.
Troubleshooting
If you use Nanoc with a Gemfile, ensure you call Nanoc as bundle exec nanoc. Nanoc no longer attempts to load the Gemfile.
-
If you get a
NoMethodError
error onNanoc::Identifier
, call.to_s
on the identifier before doing anything with it. In Nanoc 4.x, identifiers have their own class and are no longer strings.item.identifier[7..-2]
item.identifier.to_s[7..-2]
If you get a
NoMethodError
that you did not expect, you might be using a private API that is no longer present in Nanoc 4.0. In case of doubt, ask for help on GitHub discussions or the Google group.
Removed features
The watch
and autocompile
commands have been removed. Both were deprecated in Nanoc 3.6. Use guard-nanoc instead.
Because Nanoc’s focus is now more clearly on compiling content rather than managing it, the following features have been removed:
- the
create-item
andcreate-layout
commands - the
update
andsync
commands - VCS integration (along with
Nanoc::Extra::VCS
) - the
DataSource#create_item
andDataSource#create_layout
methods.