@navita/next-plugin 0.0.0 → 0.0.4

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.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Alexander Liljengård
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ import { LoaderContext } from 'webpack';
2
+
3
+ declare function pitch(this: LoaderContext<unknown>): string;
4
+
5
+ export { pitch };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ var webpackPlugin = require('@navita/webpack-plugin');
4
+
5
+ function pitch() {
6
+ this._module.loaders = [];
7
+ const dependency = webpackPlugin.getNavitaDependency(this._compiler.webpack);
8
+ const { cssHash , issuerPath } = Object.fromEntries(new URLSearchParams(this.resourceQuery).entries());
9
+ this._module.addDependency(new dependency(issuerPath, cssHash));
10
+ return '';
11
+ }
12
+
13
+ exports.pitch = pitch;
@@ -0,0 +1,11 @@
1
+ import { getNavitaDependency } from '@navita/webpack-plugin';
2
+
3
+ function pitch() {
4
+ this._module.loaders = [];
5
+ const dependency = getNavitaDependency(this._compiler.webpack);
6
+ const { cssHash , issuerPath } = Object.fromEntries(new URLSearchParams(this.resourceQuery).entries());
7
+ this._module.addDependency(new dependency(issuerPath, cssHash));
8
+ return '';
9
+ }
10
+
11
+ export { pitch };
package/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- import { NavitaPlugin } from '@navita/webpack-plugin';
1
+ import { Options } from '@navita/webpack-plugin';
2
2
  import { NextConfig } from 'next';
3
3
 
4
- type NavitaConfig = ConstructorParameters<typeof NavitaPlugin>[0];
5
- declare const createNavitaStylePlugin: (navitaConfig?: NavitaConfig) => (nextConfig: NextConfig) => NextConfig;
4
+ type WebpackOptions = Options;
5
+ interface Config extends WebpackOptions {
6
+ singleCssFile?: boolean;
7
+ }
8
+ declare const createNavitaStylePlugin: (navitaConfig?: Config) => (nextConfig: NextConfig) => NextConfig;
6
9
 
7
10
  export { createNavitaStylePlugin };
package/index.js CHANGED
@@ -1,143 +1,174 @@
1
1
  'use strict';
2
2
 
3
3
  var webpackPlugin = require('@navita/webpack-plugin');
4
- var browserslist = require('browserslist');
5
- var css = require('next/dist/build/webpack/config/blocks/css');
6
- var client = require('next/dist/build/webpack/config/blocks/css/loaders/client');
7
- var webpack = require('webpack');
4
+ var NextMiniCssExtractPlugin = require('next/dist/build/webpack/plugins/mini-css-extract-plugin');
5
+ var findPagesDir = require('next/dist/lib/find-pages-dir');
8
6
 
9
- /**
10
- * Next.js adds a cache busting query string to document:
11
- * https://github.com/vercel/next.js/blob/805183e3e93e15db7bc6c66837925431791a2250/packages/next/src/pages/_document.tsx#L481
12
- * mini-css-extract-plugin checks for existing css files with an exact match:
13
- * https://github.com/webpack-contrib/mini-css-extract-plugin/blob/2985918b79cd05cd5390e8b6d70e2009ea531521/src/index.js#L919
14
- *
15
- * This webpack plugins fixes that by adding a data-href attribute to the link tag, since that's also part
16
- * of the comparison that mini-css-extract-plugin does.
17
- */ class FixLinkTagsPlugin {
18
- static pluginName = 'FixLinkTagsPlugin';
19
- constructor(name = 'styles.css'){
20
- this.name = name;
7
+ const merge = (...merge)=>{
8
+ const result = {};
9
+ for (const usedIds of merge){
10
+ for(const key in usedIds){
11
+ result[key] = [
12
+ ...result[key] || [],
13
+ ...usedIds[key] || []
14
+ ];
15
+ }
16
+ }
17
+ for(const key1 in result){
18
+ result[key1] = [
19
+ ...new Set(result[key1])
20
+ ];
21
+ }
22
+ return result;
23
+ };
24
+ const intersect = (...intersect)=>{
25
+ const result = {};
26
+ for (const usedIds of intersect){
27
+ for(const key in usedIds){
28
+ result[key] = result[key] ? result[key].filter((id)=>usedIds[key].includes(id)) : usedIds[key];
29
+ }
21
30
  }
22
- apply(compiler) {
23
- const name = this.name;
24
- class AddDataHrefToLink extends webpack.RuntimeModule {
25
- constructor(){
26
- super(FixLinkTagsPlugin.pluginName, webpack.RuntimeModule.STAGE_NORMAL);
31
+ return result;
32
+ };
33
+ // structuredClone would be better, but since Next.js version 13 is at node 16.14.0,
34
+ // we can't use it.
35
+ const copy = (source)=>JSON.parse(JSON.stringify(source));
36
+ const removeParentFromCurrent = (usedIds, parentUsedIds)=>{
37
+ for(const key in parentUsedIds){
38
+ if (usedIds[key] && parentUsedIds[key]) {
39
+ usedIds[key] = usedIds[key].filter((id)=>!parentUsedIds[key].includes(id));
40
+ }
41
+ }
42
+ };
43
+ function optimizeCSSOutput(output) {
44
+ const nameToChunk = Object.fromEntries(Array.from(output.keys()).filter((x)=>x.name).map((x)=>[
45
+ x.name,
46
+ x
47
+ ]));
48
+ const getAllParentUsedIds = (chunk)=>{
49
+ const { name: route } = chunk;
50
+ if (!route) {
51
+ // If we don't have a name, it's a dynamic chunk.
52
+ const value = output.get(chunk);
53
+ if (value.parents.length === 0) {
54
+ return [];
55
+ }
56
+ return [
57
+ intersect(...value.parents.map((parent)=>merge(...getAllParentUsedIds(parent), output.get(parent).usedIds)))
58
+ ];
59
+ }
60
+ if (route.startsWith('pages/')) {
61
+ const routes = [
62
+ 'pages/_document',
63
+ 'pages/_app',
64
+ route
65
+ ];
66
+ const currentRouteIndex = routes.indexOf(route);
67
+ return routes.filter((_, index)=>currentRouteIndex > index).map((x)=>output.get(nameToChunk[x])).filter(Boolean).map((x)=>copy(x.usedIds));
68
+ }
69
+ const parts = route.split('/');
70
+ const parents = [];
71
+ let currentPart = '';
72
+ for (const part of parts){
73
+ currentPart = [
74
+ currentPart,
75
+ part
76
+ ].filter(Boolean).join('/');
77
+ const possibleParent = `${currentPart}/layout`;
78
+ if (route === possibleParent) {
79
+ continue;
27
80
  }
28
- generate() {
29
- return webpack.Template.asString([
30
- `const styleSheets = document.querySelectorAll('link[rel="stylesheet"][href*="${name}"]');`,
31
- `for (const styleSheet of styleSheets) {`,
32
- webpack.Template.indent(`styleSheet.setAttribute('data-href', (styleSheet.getAttribute('href') || '').replace(/\\?.*$/, ''));`),
33
- `}`
34
- ]);
81
+ const parentChunk = nameToChunk[possibleParent];
82
+ if (output.has(parentChunk)) {
83
+ parents.push(copy(output.get(parentChunk).usedIds));
35
84
  }
36
85
  }
37
- compiler.hooks.thisCompilation.tap(FixLinkTagsPlugin.pluginName, (compilation)=>{
38
- compilation.hooks.runtimeRequirementInTree.for(webpack.RuntimeGlobals.ensureChunkHandlers).tap(FixLinkTagsPlugin.pluginName, (chunk)=>{
39
- compilation.addRuntimeModule(chunk, new AddDataHrefToLink());
40
- });
86
+ return parents;
87
+ };
88
+ const newOutput = new Map();
89
+ for (const chunk of output.keys()){
90
+ const currentUsedIds = copy(output.get(chunk).usedIds);
91
+ for (const parent of getAllParentUsedIds(chunk)){
92
+ removeParentFromCurrent(currentUsedIds, parent);
93
+ }
94
+ newOutput.set(chunk, {
95
+ ...output.get(chunk),
96
+ usedIds: currentUsedIds
41
97
  });
42
98
  }
99
+ return newOutput;
43
100
  }
44
101
 
45
- function getCssLoader() {
46
- try {
47
- // v12+
48
- return require.resolve('next/dist/build/webpack/loaders/css-loader/src');
49
- } catch (_) {
50
- return 'css-loader';
51
- }
52
- }
53
-
54
- function getNextCssRules(config) {
55
- return config.module?.rules?.find((rule)=>typeof rule === "object" && Array.isArray(rule.oneOf) && rule.oneOf.some(({ test })=>test instanceof RegExp && typeof test.test === "function" && test.test("filename.css")))?.oneOf;
56
- }
57
-
58
- const NextMiniCssExtractPlugin = require("next/dist/build/webpack/plugins/mini-css-extract-plugin").default;
59
- const getSupportedBrowsers = (dir, isDevelopment)=>{
60
- try {
61
- return browserslist.loadConfig({
62
- path: dir,
63
- env: isDevelopment ? "development" : "production"
64
- });
65
- } catch {}
66
- return undefined;
67
- };
68
- const navitaStyleConfig = (nextConfig, navitaConfig = {})=>{
69
- return {
70
- webpack (config, options) {
71
- const { dir , dev , isServer } = options;
72
- const cssRules = getNextCssRules(config);
73
- const outputLoaders = [
74
- {
75
- loader: getCssLoader(),
76
- options: {
77
- postcss: ()=>css.lazyPostCSS(dir, getSupportedBrowsers(dir, dev), undefined)
102
+ const MiniCssExtractPlugin = NextMiniCssExtractPlugin['default'];
103
+ const createNavitaStylePlugin = (navitaConfig = {})=>(nextConfig)=>Object.assign({}, nextConfig, {
104
+ webpack (config, options) {
105
+ const { dir , config: resolvedNextConfig , dev } = options;
106
+ config.plugins?.push({
107
+ apply (compiler) {
108
+ // We call the getNavitaModule function here
109
+ // so that NavitaModule is created with what's required for the next.js rsc to client entry promotion:
110
+ webpackPlugin.getNavitaModule(compiler.webpack, ({ cssHash , issuerPath })=>({
111
+ // The resourceResolveData is used by next.js to promote server and ssr entries to client entries:
112
+ // https://github.com/vercel/next.js/blob/f3132354285fb18c290bf9aad7f8dc7e0550105d/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L590
113
+ resourceResolveData: {
114
+ path: '',
115
+ query: '?' + new URLSearchParams({
116
+ cssHash,
117
+ issuerPath
118
+ }).toString()
119
+ },
120
+ // We set the resource to ".css"
121
+ // to trick next.js into thinking this is a css module:
122
+ // https://github.com/vercel/next.js/blob/f3132354285fb18c290bf9aad7f8dc7e0550105d/packages/next/src/build/webpack/loaders/utils.ts#L24
123
+ resource: '.css'
124
+ }));
78
125
  }
79
- }
80
- ];
81
- if (!isServer) {
82
- const styleLoader = client.getClientStyleLoader({
83
- // False to force the use of MiniCssExtractPlugin
84
- isDevelopment: false,
85
- assetPrefix: options.config.assetPrefix,
86
- hasAppDir: false,
87
- isAppDir: false
88
126
  });
89
- outputLoaders.unshift(styleLoader);
90
- }
91
- cssRules?.unshift({
92
- test: webpackPlugin.getEmptyCssFilePath(),
93
- sideEffects: true,
94
- use: outputLoaders
95
- });
96
- // Add next's MiniCssExtractPlugin if it's not already added
97
- // todo: ^- only on development?
98
- if (!config.plugins?.some((plugin)=>plugin instanceof NextMiniCssExtractPlugin)) {
99
- // HMR reloads the CSS file when the content changes but does not use
100
- // the new file name, which means it can't contain a hash.
101
- // Logic adopted from https://git.io/JtdBy
102
- config.plugins?.push(new NextMiniCssExtractPlugin({
103
- filename: "static/css/[name].css"
104
- }));
105
- }
106
- config.plugins?.push(new webpackPlugin.NavitaPlugin({
107
- ...navitaConfig,
108
- outputCss: !isServer,
109
- isServer
110
- }));
111
- if (!isServer && dev) {
112
- config.plugins?.push(new FixLinkTagsPlugin());
113
- }
114
- if (typeof nextConfig.webpack === "function") {
115
- return nextConfig.webpack(config, options);
116
- }
117
- config.optimization = {
118
- ...config.optimization,
119
- splitChunks: {
120
- ...config.optimization?.splitChunks,
121
- cacheGroups: {
122
- ...config.optimization?.splitChunks ? config.optimization.splitChunks.cacheGroups : {},
123
- styles: {
124
- name: 'styles',
125
- type: 'css/mini-extract',
126
- test: webpackPlugin.getEmptyCssFilePath(),
127
- chunks: 'all',
128
- enforce: true
127
+ // This loader promotes the server and ssr entries to client entries:
128
+ config.module?.rules.unshift({
129
+ resourceQuery: [
130
+ /cssHash/,
131
+ /issuerPath/
132
+ ],
133
+ loader: require.resolve("@navita/next-plugin/fromServerLoader")
134
+ });
135
+ const findPagesDirResult = findPagesDir.findPagesDir(dir, !!resolvedNextConfig.experimental.appDir);
136
+ const hasAppDir = !!(findPagesDirResult && findPagesDirResult.appDir);
137
+ const isServer = options.isServer && !(options.nextRuntime === 'edge');
138
+ const outputCss = !isServer || hasAppDir;
139
+ if (!hasAppDir && !isServer) {
140
+ const filename = dev ? 'static/css/[name].css' : 'static/css/[contenthash].css';
141
+ // https://github.com/vercel/next.js/blob/930db5c1afbe541a0b2357c26123c2b365b56624/packages/next/src/build/webpack/config/blocks/css/index.ts#L595
142
+ config.plugins.push(new MiniCssExtractPlugin({
143
+ filename,
144
+ chunkFilename: filename,
145
+ ignoreOrder: true
146
+ }));
147
+ }
148
+ if (navitaConfig?.singleCssFile) {
149
+ config.optimization.splitChunks = {
150
+ ...config.optimization.splitChunks || {},
151
+ cacheGroups: {
152
+ ...config.optimization.splitChunks['cacheGroups'] || {},
153
+ navita: {
154
+ chunks: 'all',
155
+ enforce: true,
156
+ name: 'navita',
157
+ type: webpackPlugin.NAVITA_MODULE_TYPE
158
+ }
129
159
  }
130
- }
160
+ };
131
161
  }
132
- };
133
- return config;
134
- }
135
- };
136
- };
137
- const createNavitaStylePlugin = (navitaConfig = {})=>{
138
- return (nextConfig)=>{
139
- return Object.assign({}, nextConfig, navitaStyleConfig(nextConfig, navitaConfig));
140
- };
141
- };
162
+ config.plugins?.push(new webpackPlugin.NavitaPlugin({
163
+ outputCss,
164
+ ...navitaConfig,
165
+ optimizeCSSOutput
166
+ }));
167
+ if (typeof nextConfig.webpack === "function") {
168
+ return nextConfig.webpack(config, options);
169
+ }
170
+ return config;
171
+ }
172
+ });
142
173
 
143
174
  exports.createNavitaStylePlugin = createNavitaStylePlugin;
package/index.mjs CHANGED
@@ -1,143 +1,174 @@
1
1
  import { createRequire as createRequire$1 } from 'module';
2
2
  const require = createRequire$1(import.meta.url);
3
- import { getEmptyCssFilePath, NavitaPlugin } from '@navita/webpack-plugin';
4
- import { loadConfig } from 'browserslist';
5
- import { lazyPostCSS } from 'next/dist/build/webpack/config/blocks/css';
6
- import { getClientStyleLoader } from 'next/dist/build/webpack/config/blocks/css/loaders/client';
7
- import { RuntimeGlobals, RuntimeModule, Template } from 'webpack';
3
+ import { getNavitaModule, NAVITA_MODULE_TYPE, NavitaPlugin } from '@navita/webpack-plugin';
4
+ import NextMiniCssExtractPlugin from 'next/dist/build/webpack/plugins/mini-css-extract-plugin';
5
+ import { findPagesDir } from 'next/dist/lib/find-pages-dir';
8
6
 
9
- /**
10
- * Next.js adds a cache busting query string to document:
11
- * https://github.com/vercel/next.js/blob/805183e3e93e15db7bc6c66837925431791a2250/packages/next/src/pages/_document.tsx#L481
12
- * mini-css-extract-plugin checks for existing css files with an exact match:
13
- * https://github.com/webpack-contrib/mini-css-extract-plugin/blob/2985918b79cd05cd5390e8b6d70e2009ea531521/src/index.js#L919
14
- *
15
- * This webpack plugins fixes that by adding a data-href attribute to the link tag, since that's also part
16
- * of the comparison that mini-css-extract-plugin does.
17
- */ class FixLinkTagsPlugin {
18
- static pluginName = 'FixLinkTagsPlugin';
19
- constructor(name = 'styles.css'){
20
- this.name = name;
7
+ const merge = (...merge)=>{
8
+ const result = {};
9
+ for (const usedIds of merge){
10
+ for(const key in usedIds){
11
+ result[key] = [
12
+ ...result[key] || [],
13
+ ...usedIds[key] || []
14
+ ];
15
+ }
16
+ }
17
+ for(const key1 in result){
18
+ result[key1] = [
19
+ ...new Set(result[key1])
20
+ ];
21
+ }
22
+ return result;
23
+ };
24
+ const intersect = (...intersect)=>{
25
+ const result = {};
26
+ for (const usedIds of intersect){
27
+ for(const key in usedIds){
28
+ result[key] = result[key] ? result[key].filter((id)=>usedIds[key].includes(id)) : usedIds[key];
29
+ }
21
30
  }
22
- apply(compiler) {
23
- const name = this.name;
24
- class AddDataHrefToLink extends RuntimeModule {
25
- constructor(){
26
- super(FixLinkTagsPlugin.pluginName, RuntimeModule.STAGE_NORMAL);
31
+ return result;
32
+ };
33
+ // structuredClone would be better, but since Next.js version 13 is at node 16.14.0,
34
+ // we can't use it.
35
+ const copy = (source)=>JSON.parse(JSON.stringify(source));
36
+ const removeParentFromCurrent = (usedIds, parentUsedIds)=>{
37
+ for(const key in parentUsedIds){
38
+ if (usedIds[key] && parentUsedIds[key]) {
39
+ usedIds[key] = usedIds[key].filter((id)=>!parentUsedIds[key].includes(id));
40
+ }
41
+ }
42
+ };
43
+ function optimizeCSSOutput(output) {
44
+ const nameToChunk = Object.fromEntries(Array.from(output.keys()).filter((x)=>x.name).map((x)=>[
45
+ x.name,
46
+ x
47
+ ]));
48
+ const getAllParentUsedIds = (chunk)=>{
49
+ const { name: route } = chunk;
50
+ if (!route) {
51
+ // If we don't have a name, it's a dynamic chunk.
52
+ const value = output.get(chunk);
53
+ if (value.parents.length === 0) {
54
+ return [];
55
+ }
56
+ return [
57
+ intersect(...value.parents.map((parent)=>merge(...getAllParentUsedIds(parent), output.get(parent).usedIds)))
58
+ ];
59
+ }
60
+ if (route.startsWith('pages/')) {
61
+ const routes = [
62
+ 'pages/_document',
63
+ 'pages/_app',
64
+ route
65
+ ];
66
+ const currentRouteIndex = routes.indexOf(route);
67
+ return routes.filter((_, index)=>currentRouteIndex > index).map((x)=>output.get(nameToChunk[x])).filter(Boolean).map((x)=>copy(x.usedIds));
68
+ }
69
+ const parts = route.split('/');
70
+ const parents = [];
71
+ let currentPart = '';
72
+ for (const part of parts){
73
+ currentPart = [
74
+ currentPart,
75
+ part
76
+ ].filter(Boolean).join('/');
77
+ const possibleParent = `${currentPart}/layout`;
78
+ if (route === possibleParent) {
79
+ continue;
27
80
  }
28
- generate() {
29
- return Template.asString([
30
- `const styleSheets = document.querySelectorAll('link[rel="stylesheet"][href*="${name}"]');`,
31
- `for (const styleSheet of styleSheets) {`,
32
- Template.indent(`styleSheet.setAttribute('data-href', (styleSheet.getAttribute('href') || '').replace(/\\?.*$/, ''));`),
33
- `}`
34
- ]);
81
+ const parentChunk = nameToChunk[possibleParent];
82
+ if (output.has(parentChunk)) {
83
+ parents.push(copy(output.get(parentChunk).usedIds));
35
84
  }
36
85
  }
37
- compiler.hooks.thisCompilation.tap(FixLinkTagsPlugin.pluginName, (compilation)=>{
38
- compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.ensureChunkHandlers).tap(FixLinkTagsPlugin.pluginName, (chunk)=>{
39
- compilation.addRuntimeModule(chunk, new AddDataHrefToLink());
40
- });
86
+ return parents;
87
+ };
88
+ const newOutput = new Map();
89
+ for (const chunk of output.keys()){
90
+ const currentUsedIds = copy(output.get(chunk).usedIds);
91
+ for (const parent of getAllParentUsedIds(chunk)){
92
+ removeParentFromCurrent(currentUsedIds, parent);
93
+ }
94
+ newOutput.set(chunk, {
95
+ ...output.get(chunk),
96
+ usedIds: currentUsedIds
41
97
  });
42
98
  }
99
+ return newOutput;
43
100
  }
44
101
 
45
- function getCssLoader() {
46
- try {
47
- // v12+
48
- return require.resolve('next/dist/build/webpack/loaders/css-loader/src');
49
- } catch (_) {
50
- return 'css-loader';
51
- }
52
- }
53
-
54
- function getNextCssRules(config) {
55
- return config.module?.rules?.find((rule)=>typeof rule === "object" && Array.isArray(rule.oneOf) && rule.oneOf.some(({ test })=>test instanceof RegExp && typeof test.test === "function" && test.test("filename.css")))?.oneOf;
56
- }
57
-
58
- const NextMiniCssExtractPlugin = require("next/dist/build/webpack/plugins/mini-css-extract-plugin").default;
59
- const getSupportedBrowsers = (dir, isDevelopment)=>{
60
- try {
61
- return loadConfig({
62
- path: dir,
63
- env: isDevelopment ? "development" : "production"
64
- });
65
- } catch {}
66
- return undefined;
67
- };
68
- const navitaStyleConfig = (nextConfig, navitaConfig = {})=>{
69
- return {
70
- webpack (config, options) {
71
- const { dir , dev , isServer } = options;
72
- const cssRules = getNextCssRules(config);
73
- const outputLoaders = [
74
- {
75
- loader: getCssLoader(),
76
- options: {
77
- postcss: ()=>lazyPostCSS(dir, getSupportedBrowsers(dir, dev), undefined)
102
+ const MiniCssExtractPlugin = NextMiniCssExtractPlugin['default'];
103
+ const createNavitaStylePlugin = (navitaConfig = {})=>(nextConfig)=>Object.assign({}, nextConfig, {
104
+ webpack (config, options) {
105
+ const { dir , config: resolvedNextConfig , dev } = options;
106
+ config.plugins?.push({
107
+ apply (compiler) {
108
+ // We call the getNavitaModule function here
109
+ // so that NavitaModule is created with what's required for the next.js rsc to client entry promotion:
110
+ getNavitaModule(compiler.webpack, ({ cssHash , issuerPath })=>({
111
+ // The resourceResolveData is used by next.js to promote server and ssr entries to client entries:
112
+ // https://github.com/vercel/next.js/blob/f3132354285fb18c290bf9aad7f8dc7e0550105d/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L590
113
+ resourceResolveData: {
114
+ path: '',
115
+ query: '?' + new URLSearchParams({
116
+ cssHash,
117
+ issuerPath
118
+ }).toString()
119
+ },
120
+ // We set the resource to ".css"
121
+ // to trick next.js into thinking this is a css module:
122
+ // https://github.com/vercel/next.js/blob/f3132354285fb18c290bf9aad7f8dc7e0550105d/packages/next/src/build/webpack/loaders/utils.ts#L24
123
+ resource: '.css'
124
+ }));
78
125
  }
79
- }
80
- ];
81
- if (!isServer) {
82
- const styleLoader = getClientStyleLoader({
83
- // False to force the use of MiniCssExtractPlugin
84
- isDevelopment: false,
85
- assetPrefix: options.config.assetPrefix,
86
- hasAppDir: false,
87
- isAppDir: false
88
126
  });
89
- outputLoaders.unshift(styleLoader);
90
- }
91
- cssRules?.unshift({
92
- test: getEmptyCssFilePath(),
93
- sideEffects: true,
94
- use: outputLoaders
95
- });
96
- // Add next's MiniCssExtractPlugin if it's not already added
97
- // todo: ^- only on development?
98
- if (!config.plugins?.some((plugin)=>plugin instanceof NextMiniCssExtractPlugin)) {
99
- // HMR reloads the CSS file when the content changes but does not use
100
- // the new file name, which means it can't contain a hash.
101
- // Logic adopted from https://git.io/JtdBy
102
- config.plugins?.push(new NextMiniCssExtractPlugin({
103
- filename: "static/css/[name].css"
104
- }));
105
- }
106
- config.plugins?.push(new NavitaPlugin({
107
- ...navitaConfig,
108
- outputCss: !isServer,
109
- isServer
110
- }));
111
- if (!isServer && dev) {
112
- config.plugins?.push(new FixLinkTagsPlugin());
113
- }
114
- if (typeof nextConfig.webpack === "function") {
115
- return nextConfig.webpack(config, options);
116
- }
117
- config.optimization = {
118
- ...config.optimization,
119
- splitChunks: {
120
- ...config.optimization?.splitChunks,
121
- cacheGroups: {
122
- ...config.optimization?.splitChunks ? config.optimization.splitChunks.cacheGroups : {},
123
- styles: {
124
- name: 'styles',
125
- type: 'css/mini-extract',
126
- test: getEmptyCssFilePath(),
127
- chunks: 'all',
128
- enforce: true
127
+ // This loader promotes the server and ssr entries to client entries:
128
+ config.module?.rules.unshift({
129
+ resourceQuery: [
130
+ /cssHash/,
131
+ /issuerPath/
132
+ ],
133
+ loader: require.resolve("@navita/next-plugin/fromServerLoader")
134
+ });
135
+ const findPagesDirResult = findPagesDir(dir, !!resolvedNextConfig.experimental.appDir);
136
+ const hasAppDir = !!(findPagesDirResult && findPagesDirResult.appDir);
137
+ const isServer = options.isServer && !(options.nextRuntime === 'edge');
138
+ const outputCss = !isServer || hasAppDir;
139
+ if (!hasAppDir && !isServer) {
140
+ const filename = dev ? 'static/css/[name].css' : 'static/css/[contenthash].css';
141
+ // https://github.com/vercel/next.js/blob/930db5c1afbe541a0b2357c26123c2b365b56624/packages/next/src/build/webpack/config/blocks/css/index.ts#L595
142
+ config.plugins.push(new MiniCssExtractPlugin({
143
+ filename,
144
+ chunkFilename: filename,
145
+ ignoreOrder: true
146
+ }));
147
+ }
148
+ if (navitaConfig?.singleCssFile) {
149
+ config.optimization.splitChunks = {
150
+ ...config.optimization.splitChunks || {},
151
+ cacheGroups: {
152
+ ...config.optimization.splitChunks['cacheGroups'] || {},
153
+ navita: {
154
+ chunks: 'all',
155
+ enforce: true,
156
+ name: 'navita',
157
+ type: NAVITA_MODULE_TYPE
158
+ }
129
159
  }
130
- }
160
+ };
131
161
  }
132
- };
133
- return config;
134
- }
135
- };
136
- };
137
- const createNavitaStylePlugin = (navitaConfig = {})=>{
138
- return (nextConfig)=>{
139
- return Object.assign({}, nextConfig, navitaStyleConfig(nextConfig, navitaConfig));
140
- };
141
- };
162
+ config.plugins?.push(new NavitaPlugin({
163
+ outputCss,
164
+ ...navitaConfig,
165
+ optimizeCSSOutput
166
+ }));
167
+ if (typeof nextConfig.webpack === "function") {
168
+ return nextConfig.webpack(config, options);
169
+ }
170
+ return config;
171
+ }
172
+ });
142
173
 
143
174
  export { createNavitaStylePlugin };
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@navita/next-plugin",
3
- "version": "0.0.0",
3
+ "version": "0.0.4",
4
+ "license": "MIT",
5
+ "private": false,
6
+ "sideEffects": false,
4
7
  "exports": {
5
- "import": "./index.mjs",
6
- "require": "./index.js",
7
- "types": "./index.d.ts"
8
+ ".": {
9
+ "import": "./index.mjs",
10
+ "require": "./index.js",
11
+ "types": "./index.d.ts"
12
+ },
13
+ "./fromServerLoader": {
14
+ "import": "./fromServerLoader.mjs",
15
+ "require": "./fromServerLoader.js",
16
+ "types": "./fromServerLoader.d.ts"
17
+ }
8
18
  },
9
19
  "dependencies": {
10
20
  "@navita/webpack-plugin": "*",