Next.js でPreact を使う
Yuji Matsumoto / October 29 2020
https://github.com/developit/nextjs-preact-demo を参考にしながら、このブログの React を Preact で置き換えたので、やったことをメモしていく。
目次
Preact を install する
まずは Preact を install する。
$ yarn add preact preact-render-to-string
次に、React 周りの alias を設定していく。
npm install
を実行する際に、npm install <alias>@npm:<package>
のように option を指定することで、任意の alias 名で module を install することができる。 (yarn にも同様の option がある)
今回は、React 関係の module を Preact で置き換えていきたいので、以下のように command を実行する。
$ yarn add react@npm:@preact/compat@^0.0.3 react-dom@npm:@preact/compat@^0.0.3 react-ssr-prepass@npm:preact-ssr-prepass@^1.1.2
Webpack の config をする
https://github.com/developit/nextjs-preact-demo/blob/master/next.config.js からまるっとコピーしてきた。分かる限りで各 config の意味を書いていく。
experimental.modern
experimental: {
modern: true,
},
next build
で生成される build target を modern browser 向けにするかどうかの option- default は false で、ES2015+ 向けの build を生成する
- true にすると esmodules target で build を生成する (https://babeljs.io/docs/en/babel-preset-env#targetsesmodules)
- 他にも色々やっていそうなので PR を読むと良さそう (https://github.com/vercel/next.js/pull/7704/files)
splitChunks
webpack(config, { dev, isServer }) {
const splitChunks = config.optimization && config.optimization.splitChunks
if (splitChunks) {
const cacheGroups = splitChunks.cacheGroups;
const preactModules = /[\\/]node_modules[\\/](preact|preact-render-to-string|preact-context-provider)[\\/]/;
if (cacheGroups.framework) {
cacheGroups.preact = Object.assign({}, cacheGroups.framework, {
test: preactModules
});
cacheGroups.commons.name = 'framework';
}
else {
cacheGroups.preact = {
name: 'commons',
chunks: 'all',
test: preactModules
};
}
}
config.optimization.splitChunks.cacheGroups
cacheGroups.framework
は react 周りの modules を入れている chunk https://github.com/vercel/next.js/blob/308ec39f04ffd3c523b722592cc3e5e9edadbf80/packages/next/build/webpack-config.ts#L463cacheGroups.framework
を copy しながら、test だけpreactModules
に変更しているcacheGroups.commons.name
を 'framework' に変更している- commons chunk と framework を統合するため
if (cacheGroups.framework) {
cacheGroups.preact = Object.assign({}, cacheGroups.framework, {
test: preactModules,
});
cacheGroups.commons.name = 'framework';
}
- もしも
splitChunks
の無い version の webpack を使う場合は、cacheGroups.preact
を commons chunk に入れる
cacheGroups.preact = {
name: 'commons',
chunks: 'all',
test: preactModules,
};
alias
const aliases = config.resolve.alias || (config.resolve.alias = {});
aliases.react = aliases['react-dom'] = 'preact/compat';
react
react-dom
をpreact/compat
で resolve するように設定している
dev 環境向けの設定
if (dev) {
if (isServer) {
// Remove circular `__self` and `__source` props only meant for
// development. See https://github.com/developit/nextjs-preact-demo/issues/25
let oldVNodeHook = preact.options.vnode;
preact.options.vnode = (vnode) => {
const props = vnode.props;
if (props != null) {
if ('__self' in props) props.__self = null;
if ('__source' in props) props.__source = null;
}
if (oldVNodeHook) {
oldVNodeHook(vnode);
}
};
} else {
// inject Preact DevTools
const entry = config.entry;
config.entry = () =>
entry().then((entries) => {
entries['main.js'] = ['preact/debug'].concat(entries['main.js'] || []);
return entries;
});
}
}
dev
isServer
は Next.js から提供されている値
Server-side で compile される場合
if (isServer) {
// Remove circular `__self` and `__source` props only meant for
// development. See https://github.com/developit/nextjs-preact-demo/issues/25
let oldVNodeHook = preact.options.vnode;
preact.options.vnode = (vnode) => {
const props = vnode.props;
if (props != null) {
if ('__self' in props) props.__self = null;
if ('__source' in props) props.__source = null;
}
if (oldVNodeHook) {
oldVNodeHook(vnode);
}
};
}
if (isServer) { /* ... */ }
の部分が該当- Preact には Option hooks という機能があり、Preact の render の挙動に介入することができる
options.vnode
はVNode
(Preact の Virtual DOM elements) が作成された際に実行される hook- dev 環境での
Circular structure in "getInitialProps"
を防ぐために、props.__self
とprops.__source
を null にしているらしい
Client-side で compile される場合
else {
// inject Preact DevTools
const entry = config.entry;
config.entry = () =>
entry().then((entries) => {
entries['main.js'] = ['preact/debug'].concat(entries['main.js'] || []);
return entries;
});
}
else
以下の部分が該当- Preact の devtools を inject している
next.config.js の全体像
元々あった mdx / font 用の設定を併せて、現在のnext.config.js
は以下の様になっている。
// next.config.js
const preact = require('preact');
const mdxPrism = require('mdx-prism');
const withMdxEnhanced = require('next-mdx-enhanced');
const withPrefresh = require('@prefresh/next');
const config = {
experimental: {
modern: true,
},
webpack: (config, { dev, isServer }) => {
config.resolve.extensions.push('.ttf');
config.module.rules.push({
test: /\.ttf/,
loader: 'url-loader',
});
const splitChunks = config.optimization && config.optimization.splitChunks;
if (splitChunks) {
const cacheGroups = splitChunks.cacheGroups;
const preactModules = /[\\/]node_modules[\\/](preact|preact-render-to-string|preact-context-provider)[\\/]/;
if (cacheGroups.framework) {
cacheGroups.preact = Object.assign({}, cacheGroups.framework, {
test: preactModules,
});
cacheGroups.commons.name = 'framework';
} else {
cacheGroups.preact = {
name: 'commons',
chunks: 'all',
test: preactModules,
};
}
}
// Install webpack aliases:
const aliases = config.resolve.alias || (config.resolve.alias = {});
aliases.react = aliases['react-dom'] = 'preact/compat';
if (dev) {
if (isServer) {
// Remove circular `__self` and `__source` props only meant for
// development. See https://github.com/developit/nextjs-preact-demo/issues/25
let oldVNodeHook = preact.options.vnode;
preact.options.vnode = (vnode) => {
const props = vnode.props;
if (props != null) {
if ('__self' in props) props.__self = null;
if ('__source' in props) props.__source = null;
}
if (oldVNodeHook) {
oldVNodeHook(vnode);
}
};
} else {
// inject Preact DevTools
const entry = config.entry;
config.entry = () =>
entry().then((entries) => {
entries['main.js'] = ['preact/debug'].concat(
entries['main.js'] || []
);
return entries;
});
}
}
return config;
},
};
module.exports = withPrefresh(
withMdxEnhanced({
fileExtensions: ['mdx'],
layoutPath: 'src/components/layouts',
defaultLayout: true,
remarkPlugins: [
require('remark-autolink-headings'),
require('remark-slug'),
require('remark-code-titles'),
],
rehypePlugins: [mdxPrism],
})(config)
);
置き換えた結果
- https://queq1890.info/ の lighthouse の score の比較
- bundle size が小さくなった分 perf が向上している