feat: add next preset to webpack-dev-server-fresh (#21069)

Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
This commit is contained in:
Zachary Williams
2022-04-19 17:52:19 -05:00
committed by GitHub
parent 48ebe6444b
commit ccfee1ed13
34 changed files with 6636 additions and 219 deletions

View File

@@ -0,0 +1,74 @@
// <reference types="cypress" />
/// <reference path="../support/e2e.ts" />
import type { ProjectFixtureDir } from '@tooling/system-tests/lib/fixtureDirs'
const WEBPACK_REACT: ProjectFixtureDir[] = ['next-11', 'next-12', 'next-11-webpack-4']
// Add to this list to focus on a particular permutation
const ONLY_PROJECTS: ProjectFixtureDir[] = []
for (const project of WEBPACK_REACT) {
if (ONLY_PROJECTS.length && !ONLY_PROJECTS.includes(project)) {
continue
}
describe(`Working with ${project}`, () => {
beforeEach(() => {
cy.scaffoldProject(project)
cy.openProject(project)
cy.startAppServer('component')
})
it('should mount a passing test', () => {
cy.visitApp()
cy.contains('index.cy.js').click()
cy.get('.passed > .num').should('contain', 1)
})
it('should live-reload on src changes', () => {
cy.visitApp()
cy.contains('index.cy.js').click()
cy.get('.passed > .num').should('contain', 1)
cy.withCtx(async (ctx) => {
const indexPath = ctx.path.join('pages', 'index.js')
await ctx.actions.file.writeFileInProject(
indexPath,
(await ctx.file.readFileInProject(indexPath)).replace('Welcome to', 'Hello from'),
)
})
cy.get('.failed > .num', { timeout: 10000 }).should('contain', 1)
cy.withCtx(async (ctx) => {
const indexTestPath = ctx.path.join('pages', 'index.cy.js')
await ctx.actions.file.writeFileInProject(
indexTestPath,
(await ctx.file.readFileInProject(indexTestPath)).replace('Welcome to', 'Hello from'),
)
})
cy.get('.passed > .num').should('contain', 1)
})
it('should detect new spec', () => {
cy.visitApp()
cy.withCtx(async (ctx) => {
const newTestPath = ctx.path.join('pages', 'New.cy.js')
const indexTestPath = ctx.path.join('pages', 'index.cy.js')
await ctx.actions.file.writeFileInProject(
newTestPath,
await ctx.file.readFileInProject(indexTestPath),
)
})
cy.contains('New.cy.js').click()
cy.get('.passed > .num').should('contain', 1)
})
})
}

View File

@@ -4,13 +4,14 @@ import type WebpackDevServer from 'webpack-dev-server'
import type { Compiler, Configuration } from 'webpack'
import { createWebpackDevServer } from './createWebpackDevServer'
import { sourceRelativeWebpackModules } from './helpers/sourceRelativeWebpackModules'
import type { AddressInfo } from 'net'
import debugLib from 'debug'
import type { Server } from 'http'
import { vueCliHandler } from './helpers/vueCliHandler'
import { nuxtHandler } from './helpers/nuxtHandler'
import { createReactAppHandler } from './helpers/createReactAppHandler'
import { nextHandler } from './helpers/nextHandler'
import { sourceDefaultWebpackDependencies, SourceRelativeWebpackResult } from './helpers/sourceRelativeWebpackModules'
const debug = debugLib('cypress:webpack-dev-server-fresh:devServer')
@@ -23,7 +24,7 @@ export type WebpackDevServerConfig = {
webpackConfig?: unknown // Derived from the user's webpack
}
const ALL_FRAMEWORKS = ['create-react-app', 'nuxt', 'react', 'vue-cli'] as const
const ALL_FRAMEWORKS = ['create-react-app', 'nuxt', 'react', 'vue-cli', 'next', 'vue'] as const
/**
* @internal
@@ -111,6 +112,31 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp
})
}
export type PresetHandlerResult = { frameworkConfig?: Configuration, sourceWebpackModulesResult: SourceRelativeWebpackResult }
async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<PresetHandlerResult> {
switch (devServerConfig.framework) {
case 'create-react-app':
return createReactAppHandler(devServerConfig)
case 'nuxt':
return await nuxtHandler(devServerConfig)
case 'vue-cli':
return vueCliHandler(devServerConfig)
case 'next':
return await nextHandler(devServerConfig)
case 'react':
case 'vue':
case undefined:
return { sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
default:
throw new Error(`Unexpected framework ${devServerConfig.framework}, expected one of ${ALL_FRAMEWORKS.join(', ')}`)
}
}
/**
* Synchronously create the webpack server instance, without starting.
* Useful for testing
@@ -118,29 +144,7 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp
* @internal
*/
devServer.create = async function (devServerConfig: WebpackDevServerConfig) {
const sourceWebpackModulesResult = sourceRelativeWebpackModules(devServerConfig)
let frameworkConfig: Configuration | undefined
// If we have a framework specified, source the associated config
if (typeof devServerConfig.framework === 'string') {
switch (devServerConfig.framework) {
case 'create-react-app':
frameworkConfig = createReactAppHandler({ devServerConfig, sourceWebpackModulesResult })
break
case 'react':
break
case 'nuxt':
frameworkConfig = await nuxtHandler({ devServerConfig, sourceWebpackModulesResult })
break
case 'vue-cli':
frameworkConfig = vueCliHandler({ devServerConfig, sourceWebpackModulesResult })
break
default:
throw new Error(`Unexpected framework ${devServerConfig.framework}, expected one of ${ALL_FRAMEWORKS.join(', ')}`)
}
}
const { frameworkConfig, sourceWebpackModulesResult } = await getPreset(devServerConfig)
const { server, compiler } = createWebpackDevServer({
devServerConfig,

View File

@@ -1,23 +1,22 @@
import type { CreateFinalWebpackConfig } from '../createWebpackDevServer'
import debugLib from 'debug'
import type { Configuration, ResolvePluginInstance, RuleSetRule } from 'webpack'
import path from 'path'
type PresetHandlerOptions = Omit<CreateFinalWebpackConfig, 'frameworkConfig'>
import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer'
import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules'
const debug = debugLib('cypress:webpack-dev-server-fresh:create-react-app')
/**
* Sourcing the config for create-react-app
*/
export function createReactAppHandler (presetHandler: PresetHandlerOptions) {
const { devServerConfig, sourceWebpackModulesResult } = presetHandler
export function createReactAppHandler (devServerConfig: WebpackDevServerConfig): PresetHandlerResult {
const sourceWebpackModulesResult = sourceDefaultWebpackDependencies(devServerConfig)
// this is required because
// 1) we use our own HMR and we don't need react-refresh transpiling overhead
// 2) it doesn't work with process.env=test @see https://github.com/cypress-io/cypress-realworld-app/pull/832
process.env.FAST_REFRESH = 'false'
const webpackConfig = loadWebpackConfig(presetHandler)
const webpackConfig = loadWebpackConfig(devServerConfig)
addCypressToWebpackEslintRulesInPlace(webpackConfig)
@@ -32,10 +31,13 @@ export function createReactAppHandler (presetHandler: PresetHandlerOptions) {
reactScriptsFiveModifications(webpackConfig)
}
return webpackConfig
return {
frameworkConfig: webpackConfig,
sourceWebpackModulesResult,
}
}
function loadWebpackConfig ({ devServerConfig }: PresetHandlerOptions): Configuration {
function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Configuration {
let webpackConfigPath: string
const envName = 'test'

View File

@@ -0,0 +1,250 @@
import debugLib from 'debug'
import Module from 'module'
import type { Configuration } from 'webpack'
import * as fs from 'fs'
import * as path from 'path'
import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer'
import { cypressWebpackPath, getMajorVersion, ModuleClass, SourcedDependency, SourcedWebpack, sourceFramework, sourceHtmlWebpackPlugin, sourceWebpackDevServer } from './sourceRelativeWebpackModules'
const debug = debugLib('cypress:webpack-dev-server-fresh:nextHandler')
export async function nextHandler (devServerConfig: WebpackDevServerConfig): Promise<PresetHandlerResult> {
const webpackConfig = await loadWebpackConfig(devServerConfig)
debug('resolved next.js webpack config %o', webpackConfig)
checkSWC(webpackConfig, devServerConfig.cypressConfig)
// Next webpack compiler ignored watching any node_modules changes, but we need to watch
// for changes to 'dist/browser.js' in order to detect new specs that have been added
if (webpackConfig.watchOptions && Array.isArray(webpackConfig.watchOptions.ignored)) {
webpackConfig.watchOptions = {
...webpackConfig.watchOptions,
ignored: [...webpackConfig.watchOptions.ignored.filter((pattern: string) => !/node_modules/.test(pattern)), '**/node_modules/!(@cypress/webpack-dev-server/dist/browser.js)**'],
}
debug('found options next.js watchOptions.ignored %O', webpackConfig.watchOptions.ignored)
}
return { frameworkConfig: webpackConfig, sourceWebpackModulesResult: sourceNextWebpackDeps(devServerConfig) }
}
/**
* Acquire the modules needed to load the Next webpack config. We are using Next's APIs to grab the webpackConfig
* but since this is in the binary, we have to `require.resolve` them from the projectRoot
* `loadConfig` acquires the next.config.js
* `getNextJsBaseWebpackConfig` acquires the webpackConfig dependent on the next.config.js
*/
function getNextJsPackages (devServerConfig: WebpackDevServerConfig) {
const resolvePaths = { paths: [devServerConfig.cypressConfig.projectRoot] }
const packages = {} as { loadConfig: Function, getNextJsBaseWebpackConfig: Function }
try {
const loadConfigPath = require.resolve('next/dist/server/config', resolvePaths)
packages.loadConfig = require(loadConfigPath).default
} catch (e: any) {
throw new Error(`Failed to load "next/dist/server/config" with error: ${e.message ?? e}`)
}
try {
const getNextJsBaseWebpackConfigPath = require.resolve('next/dist/build/webpack-config', resolvePaths)
packages.getNextJsBaseWebpackConfig = require(getNextJsBaseWebpackConfigPath).default
} catch (e: any) {
throw new Error(`Failed to load "next/dist/build/webpack-config" with error: ${ e.message ?? e}`)
}
return packages
}
async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Promise<Configuration> {
const { loadConfig, getNextJsBaseWebpackConfig } = getNextJsPackages(devServerConfig)
const nextConfig = await loadConfig('development', devServerConfig.cypressConfig.projectRoot)
const runWebpackSpan = await getRunWebpackSpan()
const webpackConfig = await getNextJsBaseWebpackConfig(
devServerConfig.cypressConfig.projectRoot,
{
buildId: `@cypress/react-${Math.random().toString()}`,
config: nextConfig,
dev: true,
isServer: false,
pagesDir: findPagesDir(devServerConfig.cypressConfig.projectRoot),
entrypoints: {},
rewrites: { fallback: [], afterFiles: [], beforeFiles: [] },
...runWebpackSpan,
},
)
return webpackConfig
}
/**
* Check if Next is using the SWC compiler. Compilation will fail if user has `nodeVersion: "bundled"` set
* due to SWC certificate issues.
*/
function checkSWC (
webpackConfig: Configuration,
cypressConfig: Cypress.PluginConfigOptions,
) {
const hasSWCLoader = webpackConfig.module?.rules?.some((rule) => {
return typeof rule !== 'string' && rule.oneOf?.some(
(oneOf) => (oneOf.use as any)?.loader === 'next-swc-loader',
)
})
// "resolvedNodePath" is only set when using the user's Node.js, which is required to compile Next.js with SWC optimizations
// If it is not set, they have either explicitly set "nodeVersion" to "bundled" or are are using Cypress < 9.0.0 where it was set to "bundled" by default
if (hasSWCLoader && cypressConfig.nodeVersion === 'bundled') {
throw new Error(`Cypress cannot compile your Next.js application when "nodeVersion" is set to "bundled". Please remove this option from your Cypress configuration file.`)
}
return false
}
const existsSync = (file: string) => {
try {
fs.accessSync(file, fs.constants.F_OK)
return true
} catch (_) {
return false
}
}
/**
* Next allows the `pages` directory to be located at either
* `${projectRoot}/pages` or `${projectRoot}/src/pages`.
* If neither is found, return projectRoot
*/
function findPagesDir (projectRoot: string) {
// prioritize ./pages over ./src/pages
let pagesDir = path.join(projectRoot, 'pages')
if (existsSync(pagesDir)) {
return pagesDir
}
pagesDir = path.join(projectRoot, 'src', 'pages')
if (existsSync(pagesDir)) {
return pagesDir
}
return projectRoot
}
// Starting with v11.1.1, a trace is required.
// 'next/dist/telemetry/trace/trace' only exists since v10.0.9
// and our peerDeps support back to v8 so try-catch this import
// Starting from 12.0 trace is now located in 'next/dist/trace/trace'
async function getRunWebpackSpan (): Promise<{ runWebpackSpan?: any }> {
let trace: (name: string) => any
try {
try {
trace = await import('next/dist/telemetry/trace/trace').then((m) => m.trace)
return { runWebpackSpan: trace('cypress') }
} catch (_) {
// @ts-ignore
trace = await import('next/dist/trace/trace').then((m) => m.trace)
return { runWebpackSpan: trace('cypress') }
}
} catch (_) {
return {}
}
}
const originalModuleLoad = (Module as ModuleClass)._load
function sourceNextWebpackDeps (devServerConfig: WebpackDevServerConfig) {
const framework = sourceFramework(devServerConfig)!
const webpack = sourceNextWebpack(devServerConfig, framework)
const webpackDevServer = sourceWebpackDevServer(devServerConfig, framework)
const htmlWebpackPlugin = sourceHtmlWebpackPlugin(devServerConfig, framework, webpack)
return {
framework,
webpack,
webpackDevServer,
htmlWebpackPlugin,
}
}
function sourceNextWebpack (devServerConfig: WebpackDevServerConfig, framework: SourcedDependency) {
const searchRoot = framework.importPath
debug('NextWebpack: Attempting to load NextWebpack from %s', searchRoot)
let webpackJsonPath: string
const webpack = {} as SourcedWebpack
try {
webpackJsonPath = require.resolve('next/dist/compiled/webpack/package.json', {
paths: [searchRoot],
})
} catch (e) {
debug('NextWebpack: Failed to load NextWebpack - %s', e)
throw e
}
// Next 11 allows the choice of webpack@4 or webpack@5, depending on the "webpack5" property in their next.config.js
// The webpackModule.init" for Next 11 returns a webpack@4 or webpack@4 compiler instance based on this boolean
let webpack5 = true
const importPath = path.join(path.dirname(webpackJsonPath), 'webpack.js')
const webpackModule = require(importPath)
try {
const nextConfig = require(path.resolve(devServerConfig.cypressConfig.projectRoot, 'next.config.js'))
debug('NextWebpack: next.config.js found - %o', nextConfig)
if (nextConfig.webpack5 === false) {
webpack5 = false
}
} catch (e) {
// No next.config.js, assume webpack 5
}
debug('NextWebpack: webpack5 - %s', webpack5)
webpackModule.init(webpack5)
const packageJson = require(webpackJsonPath)
webpack.importPath = importPath
// The package.json of "next/dist/compiled/webpack/package.json" has no version so we supply the version for later use
webpack.packageJson = { ...packageJson, version: webpack5 ? '5' : '4' }
webpack.module = webpackModule.webpack
webpack.majorVersion = getMajorVersion(webpack.packageJson, [4, 5])
debug('NextWebpack: Successfully loaded NextWebpack - %o', webpack)
;(Module as ModuleClass)._load = function (request, parent, isMain) {
// Next with webpack@4 doesn't ship certain dependencies that HtmlWebpackPlugin requires, so we patch the resolution through to our bundled version
if ((request === 'webpack' || request.startsWith('webpack/')) && webpack.majorVersion === 4) {
const resolvePath = require.resolve(request, {
paths: [cypressWebpackPath],
})
debug('NextWebpack: Module._load for webpack@4 - %s', resolvePath)
return originalModuleLoad(resolvePath, parent, isMain)
}
if (request === 'webpack' || request.startsWith('webpack/')) {
const resolvePath = require.resolve(request, {
paths: [framework.importPath],
})
debug('NextWebpack: Module._load - %s', resolvePath)
return originalModuleLoad(resolvePath, parent, isMain)
}
return originalModuleLoad(request, parent, isMain)
}
return webpack
}

View File

@@ -1,12 +1,12 @@
import type { CreateFinalWebpackConfig } from '../createWebpackDevServer'
import debugLib from 'debug'
import type { Configuration } from 'webpack'
type PresetHandler = Omit<CreateFinalWebpackConfig, 'frameworkConfig'>
import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer'
import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules'
const debug = debugLib('cypress:webpack-dev-server-fresh:nuxtHandler')
export async function nuxtHandler ({ devServerConfig }: PresetHandler): Promise<Configuration> {
export async function nuxtHandler (devServerConfig: WebpackDevServerConfig): Promise<PresetHandlerResult> {
const sourceWebpackModulesResult = sourceDefaultWebpackDependencies(devServerConfig)
try {
const nuxt = require.resolve('nuxt', {
paths: [devServerConfig.cypressConfig.projectRoot],
@@ -22,7 +22,7 @@ export async function nuxtHandler ({ devServerConfig }: PresetHandler): Promise<
debug('webpack config %o', webpackConfig)
return webpackConfig
return { frameworkConfig: webpackConfig, sourceWebpackModulesResult }
} catch (e) {
console.error(e) // eslint-disable-line no-console
throw Error(`Error loading nuxt. Looked in ${require.resolve.paths(devServerConfig.cypressConfig.projectRoot)}`)

View File

@@ -5,7 +5,7 @@ import debugFn from 'debug'
const debug = debugFn('cypress:webpack-dev-server-fresh:sourceRelativeWebpackModules')
type ModuleClass = typeof Module & {
export type ModuleClass = typeof Module & {
_load(id: string, parent: Module, isMain: boolean): any
_resolveFilename(request: string, parent: Module, isMain: boolean, options?: { paths: string[] }): string
_cache: Record<string, Module>
@@ -16,97 +16,89 @@ export interface PackageJson {
version: string
}
export interface SourcedDependency {
importPath: string
packageJson: PackageJson
}
export interface SourcedWebpack extends SourcedDependency {
module: Function
majorVersion: 4 | 5
}
export interface SourcedWebpackDevServer extends SourcedDependency {
module: {
new (...args: unknown[]): unknown
}
majorVersion: 3 | 4
}
export interface SourcedHtmlWebpackPlugin extends SourcedDependency {
module: unknown
majorVersion: 4 | 5
}
export interface SourceRelativeWebpackResult {
framework?: {
importPath: string
packageJson: PackageJson
}
/**
* The webpack module instance
*/
webpack: {
importPath: string
module: Function
packageJson: PackageJson
majorVersion: 4 | 5
}
/**
* The webpack dev-server instance
*/
webpackDevServer: {
importPath: string
module: {
new (...args: unknown[]): unknown
}
packageJson: PackageJson
majorVersion: 3 | 4
}
/**
* html-webpack-plugin
*/
htmlWebpackPlugin: {
importPath: string
module: unknown
packageJson: PackageJson
majorVersion: 4 | 5
}
framework: SourcedDependency | null
webpack: SourcedWebpack
webpackDevServer: SourcedWebpackDevServer
htmlWebpackPlugin: SourcedHtmlWebpackPlugin
}
const originalModuleLoad = (Module as ModuleClass)._load
const originalModuleResolveFilename = (Module as ModuleClass)._resolveFilename
/**
* Based on the current project config, we look for the closest webpack,
* webpack-dev-server, and html-webpack-plugin for a user's project
*
* @internal
*/
export function sourceRelativeWebpackModules (config: WebpackDevServerConfig) {
let searchRoot = config.cypressConfig.projectRoot
const result = {
webpackDevServer: {},
webpack: {},
htmlWebpackPlugin: {},
} as SourceRelativeWebpackResult
// We ship webpack@4 as part of '@cypress/webpack-batteries-included-preprocessor'. The path to this module
// serves as our fallback.
export const cypressWebpackPath = require.resolve('@cypress/webpack-batteries-included-preprocessor', {
paths: [__dirname],
})
// First, we source the framework, ensuring it's sourced from the user's project and not the
// Cypress binary. This is the path we use to relative-resolve the
// This is generally used for Create React App and Vue CLI and other packages
// that ship webpack as a dependency. e.g. your-project/node_modules/react-scripts/node_modules/webpack
// So what we do, is we grab the framework's path, and try and find webpack relative to that framework.
if (config.framework) {
try {
const frameworkJsonPath = require.resolve(`${config.framework}/package.json`, {
paths: [searchRoot],
})
// Source the users framework from the provided projectRoot. The framework, if available, will server
// as the resolve base for webpack dependency resolution.
export function sourceFramework (config: WebpackDevServerConfig): SourcedDependency | null {
debug('Framework: Attempting to source framework for %s', config.cypressConfig.projectRoot)
if (!config.framework) {
debug('Framework: No framework provided')
debug('Framework JSON path is %s', frameworkJsonPath)
const frameworkPathRoot = path.dirname(frameworkJsonPath)
debug('Framework JSON path root is %s', frameworkPathRoot)
// Want to make sure we're sourcing this from the user's code. Otherwise we can
// warn and tell them they don't have their dependencies installed
if (!frameworkPathRoot.includes(config.cypressConfig.cypressBinaryRoot)) {
result.framework = {
importPath: frameworkPathRoot,
packageJson: require(frameworkJsonPath),
}
searchRoot = frameworkPathRoot
}
} catch (e) {
debug('Error %o', e)
// TODO
}
return null
}
// Webpack:
// At this point, we know where we're looking for webpack!
// We've made accommodations for certain frameworks that bundle it in (e.g. react-scripts)
let webpackJsonPath: string
const framework = { } as SourcedDependency
debug('search root is %s', searchRoot)
try {
const frameworkJsonPath = require.resolve(`${config.framework}/package.json`, {
paths: [config.cypressConfig.projectRoot],
})
const frameworkPathRoot = path.dirname(frameworkJsonPath)
// Want to make sure we're sourcing this from the user's code. Otherwise we can
// warn and tell them they don't have their dependencies installed
framework.importPath = frameworkPathRoot
framework.packageJson = require(frameworkJsonPath)
debug('Framework: Successfully sourced framework - %o', framework)
return framework
} catch (e) {
debug('Framework: Failed to source framework - %s', e)
// TODO
return null
}
}
// Source the webpack module from the provided framework or projectRoot. We override the module resolution
// so that other packages that import webpack resolve to the version we found.
// If none is found, we fallback to the bundled version in '@cypress/webpack-batteries-included-preprocessor'.
export function sourceWebpack (config: WebpackDevServerConfig, framework: SourcedDependency | null): SourcedWebpack {
const searchRoot = framework?.importPath ?? config.cypressConfig.projectRoot
debug('Webpack: Attempting to source webpack from %s', searchRoot)
const webpack = { } as SourcedWebpack
let webpackJsonPath: string
try {
webpackJsonPath = require.resolve('webpack/package.json', {
@@ -114,53 +106,63 @@ export function sourceRelativeWebpackModules (config: WebpackDevServerConfig) {
})
} catch (e) {
if ((e as {code?: string}).code !== 'MODULE_NOT_FOUND') {
debug('Webpack: Failed to source webpack - %s', e)
throw e
}
webpackJsonPath = require.resolve('webpack/package.json', {
paths: [
require.resolve('@cypress/webpack-batteries-included-preprocessor', {
paths: [__dirname],
}),
],
})
debug('Webpack: Falling back to bundled version')
debug('using webpack-batteries-included %s', webpackJsonPath)
webpackJsonPath = require.resolve('webpack/package.json', {
paths: [cypressWebpackPath],
})
}
result.webpack.importPath = path.dirname(webpackJsonPath)
result.webpack.packageJson = require(webpackJsonPath)
result.webpack.module = require(result.webpack.importPath)
result.webpack.majorVersion = getMajorVersion(result.webpack.packageJson, [4, 5])
webpack.importPath = path.dirname(webpackJsonPath)
webpack.packageJson = require(webpackJsonPath)
webpack.module = require(webpack.importPath)
webpack.majorVersion = getMajorVersion(webpack.packageJson, [4, 5])
const webpackImportPath = result.webpack.importPath
debug('Webpack: Successfully sourced webpack - %o', webpack)
;(Module as ModuleClass)._load = function (request, parent, isMain) {
if (request === 'webpack' || request.startsWith('webpack/')) {
const resolvePath = require.resolve(request, {
paths: [webpackImportPath],
paths: [webpack.importPath],
})
debug('Resolve path %s', resolvePath)
debug('Webpack: Module._load resolvePath - %s', resolvePath)
return originalModuleLoad(resolvePath, parent, isMain)
}
return originalModuleLoad(request, parent, isMain)
};
}
(Module as ModuleClass)._resolveFilename = function (request, parent, isMain, options) {
;(Module as ModuleClass)._resolveFilename = function (request, parent, isMain, options) {
if (request === 'webpack' || request.startsWith('webpack/') && !options?.paths) {
return originalModuleResolveFilename(request, parent, isMain, {
paths: [webpackImportPath],
const resolveFilename = originalModuleResolveFilename(request, parent, isMain, {
paths: [webpack.importPath],
})
debug('Webpack: Module._resolveFilename resolveFilename - %s', resolveFilename)
return resolveFilename
}
return originalModuleResolveFilename(request, parent, isMain, options)
}
// Webpack dev server:
return webpack
}
// Source the webpack-dev-server module from the provided framework or projectRoot.
// If none is found, we fallback to the version bundled with this package.
export function sourceWebpackDevServer (config: WebpackDevServerConfig, framework?: SourcedDependency | null): SourcedWebpackDevServer {
const searchRoot = framework?.importPath ?? config.cypressConfig.projectRoot
debug('WebpackDevServer: Attempting to source webpack-dev-server from %s', searchRoot)
const webpackDevServer = { } as SourcedWebpackDevServer
let webpackDevServerJsonPath: string
try {
@@ -169,23 +171,36 @@ export function sourceRelativeWebpackModules (config: WebpackDevServerConfig) {
})
} catch (e) {
if ((e as {code?: string}).code !== 'MODULE_NOT_FOUND') {
debug('WebpackDevServer: Failed to source webpack-dev-server - %s', e)
throw e
}
debug('WebpackDevServer: Falling back to bundled version')
webpackDevServerJsonPath = require.resolve('webpack-dev-server/package.json', {
paths: [
__dirname,
],
paths: [cypressWebpackPath],
})
}
result.webpackDevServer.importPath = path.dirname(webpackDevServerJsonPath)
result.webpackDevServer.packageJson = require(webpackDevServerJsonPath)
result.webpackDevServer.module = require(result.webpackDevServer.importPath)
result.webpackDevServer.majorVersion = getMajorVersion(result.webpackDevServer.packageJson, [3, 4])
webpackDevServer.importPath = path.dirname(webpackDevServerJsonPath)
webpackDevServer.packageJson = require(webpackDevServerJsonPath)
webpackDevServer.module = require(webpackDevServer.importPath)
webpackDevServer.majorVersion = getMajorVersion(webpackDevServer.packageJson, [3, 4])
// Webpack HTML Plugin:
debug('WebpackDevServer: Successfully sourced webpack-dev-server - %o', webpackDevServer)
return webpackDevServer
}
// Source the html-webpack-plugin module from the provided framework or projectRoot.
// If none is found, we fallback to the version bundled with this package dependent on the major version of webpack.
// We ship both v4 and v5 of 'html-webpack-plugin' by aliasing the package with the major version (check package.json).
export function sourceHtmlWebpackPlugin (config: WebpackDevServerConfig, framework: SourcedDependency | null, webpack: SourcedWebpack): SourcedHtmlWebpackPlugin {
const searchRoot = framework?.importPath ?? config.cypressConfig.projectRoot
debug('HtmlWebpackPlugin: Attempting to source html-webpack-plugin from %s', searchRoot)
const htmlWebpackPlugin = { } as SourcedHtmlWebpackPlugin
let htmlWebpackPluginJsonPath: string
try {
@@ -193,18 +208,21 @@ export function sourceRelativeWebpackModules (config: WebpackDevServerConfig) {
paths: [searchRoot],
})
result.htmlWebpackPlugin.packageJson = require(htmlWebpackPluginJsonPath)
htmlWebpackPlugin.packageJson = require(htmlWebpackPluginJsonPath)
// Check that they're not using v3 of html-webpack-plugin. Since we should be the only consumer of it,
// we shouldn't be concerned with using our own copy if they've shipped w/ an earlier version
result.htmlWebpackPlugin.majorVersion = getMajorVersion(result.htmlWebpackPlugin.packageJson, [4, 5])
htmlWebpackPlugin.majorVersion = getMajorVersion(htmlWebpackPlugin.packageJson, [4, 5])
} catch (e) {
const err = e as Error & {code?: string}
if (err.code !== 'MODULE_NOT_FOUND' && !err.message.includes('Unexpected major version')) {
debug('HtmlWebpackPlugin: Failed to source html-webpack-plugin - %s', e)
throw e
}
const htmlWebpack = `html-webpack-plugin-${result.webpack.majorVersion}`
const htmlWebpack = `html-webpack-plugin-${webpack.majorVersion}`
debug('HtmlWebpackPlugin: Falling back to bundled version %s', htmlWebpack)
htmlWebpackPluginJsonPath = require.resolve(`${htmlWebpack}/package.json`, {
paths: [
@@ -213,15 +231,32 @@ export function sourceRelativeWebpackModules (config: WebpackDevServerConfig) {
})
}
result.htmlWebpackPlugin.importPath = path.dirname(htmlWebpackPluginJsonPath)
result.htmlWebpackPlugin.packageJson = require(htmlWebpackPluginJsonPath)
result.htmlWebpackPlugin.module = require(result.htmlWebpackPlugin.importPath)
result.htmlWebpackPlugin.majorVersion = getMajorVersion(result.htmlWebpackPlugin.packageJson, [4, 5])
htmlWebpackPlugin.importPath = path.dirname(htmlWebpackPluginJsonPath),
htmlWebpackPlugin.packageJson = require(htmlWebpackPluginJsonPath),
htmlWebpackPlugin.module = require(htmlWebpackPlugin.importPath),
htmlWebpackPlugin.majorVersion = getMajorVersion(htmlWebpackPlugin.packageJson, [4, 5])
return result
debug('HtmlWebpackPlugin: Successfully sourced html-webpack-plugin - %o', htmlWebpackPlugin)
return htmlWebpackPlugin
}
function getMajorVersion <T extends number> (json: PackageJson, acceptedVersions: T[]): T {
// Most frameworks follow a similar path for sourcing webpack dependencies so this is a utility to handle all the sourcing.
export function sourceDefaultWebpackDependencies (config: WebpackDevServerConfig): SourceRelativeWebpackResult {
const framework = sourceFramework(config)
const webpack = sourceWebpack(config, framework)
const webpackDevServer = sourceWebpackDevServer(config, framework)
const htmlWebpackPlugin = sourceHtmlWebpackPlugin(config, framework, webpack)
return {
framework,
webpack,
webpackDevServer,
htmlWebpackPlugin,
}
}
export function getMajorVersion <T extends number> (json: PackageJson, acceptedVersions: T[]): T {
const major = Number(json.version.split('.')[0])
if (!acceptedVersions.includes(major as T)) {

View File

@@ -1,22 +1,23 @@
import type { CreateFinalWebpackConfig } from '../createWebpackDevServer'
import debugLib from 'debug'
import type { Configuration } from 'webpack'
type PresetHandler = Omit<CreateFinalWebpackConfig, 'frameworkConfig'>
import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer'
import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules'
const debug = debugLib('cypress:webpack-dev-server-fresh:vueCliHandler')
export function vueCliHandler ({ devServerConfig }: PresetHandler): Configuration {
export function vueCliHandler (devServerConfig: WebpackDevServerConfig): PresetHandlerResult {
const sourceWebpackModulesResult = sourceDefaultWebpackDependencies(devServerConfig)
try {
const config = require.resolve('@vue/cli-service/webpack.config', {
paths: [devServerConfig.cypressConfig.projectRoot],
})
const webpackConfig = require(config)
const webpackConfig = require(config) as Configuration
debug('webpack config %o', webpackConfig)
return webpackConfig
return { frameworkConfig: webpackConfig, sourceWebpackModulesResult }
} catch (e) {
console.error(e) // eslint-disable-line no-console
throw Error(`Error loading @vue/cli-service/webpack.config.js. Looked in ${require.resolve.paths(devServerConfig.cypressConfig.projectRoot)}`)

View File

@@ -7,6 +7,7 @@ import fs from 'fs'
import { devServer } from '..'
import { restoreLoadHook } from '../src/helpers/sourceRelativeWebpackModules'
import './support'
const requestSpecFile = (file: string, port: number) => {
return new Promise((res) => {

View File

@@ -17,7 +17,7 @@ describe('devServer', function () {
it('creates a new devServer webpack4, webpackDevServer3', async () => {
const { devServer } = proxyquire('../src/devServer', {
'./helpers/sourceRelativeWebpackModules': {
sourceRelativeWebpackModules: () => {
sourceDefaultWebpackDependencies: () => {
return createModuleMatrixResult({
webpack: 4,
webpackDevServer: 3,
@@ -38,7 +38,7 @@ describe('devServer', function () {
it('creates a new devServer webpack4, webpackDevServer4', async () => {
const { devServer } = proxyquire('../src/devServer', {
'./helpers/sourceRelativeWebpackModules': {
sourceRelativeWebpackModules: () => {
sourceDefaultWebpackDependencies: () => {
return createModuleMatrixResult({
webpack: 4,
webpackDevServer: 4,
@@ -59,7 +59,7 @@ describe('devServer', function () {
it('creates a new devServer webpack5, webpackDevServer4', async () => {
const { devServer } = proxyquire('../src/devServer', {
'./helpers/sourceRelativeWebpackModules': {
sourceRelativeWebpackModules: () => {
sourceDefaultWebpackDependencies: () => {
return createModuleMatrixResult({
webpack: 5,
webpackDevServer: 4,

View File

@@ -1,10 +1,10 @@
import { scaffoldMigrationProject } from '../test-helpers/scaffoldProject'
import { expect } from 'chai'
import { createReactAppHandler, cypressGlobals } from '../../src/helpers/createReactAppHandler'
import { SourceRelativeWebpackResult } from '../../src/helpers/sourceRelativeWebpackModules'
import { WebpackDevServerConfig } from '../../src/devServer'
import { Configuration } from 'webpack'
import * as path from 'path'
import '../support'
const expectEslintModifications = (webpackConfig: Configuration) => {
const eslintPlugin: any = webpackConfig.plugins?.find((plugin) => plugin.constructor.name === 'ESLintWebpackPlugin')
@@ -55,12 +55,9 @@ describe('createReactAppHandler', function () {
process.chdir(projectRoot)
const webpackConfig = createReactAppHandler({
devServerConfig: {
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig,
sourceWebpackModulesResult: { webpack: { majorVersion: 4 } } as SourceRelativeWebpackResult,
})
const { frameworkConfig: webpackConfig } = createReactAppHandler({
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
expect(webpackConfig.mode).eq('development')
expectEslintModifications(webpackConfig)
@@ -73,12 +70,9 @@ describe('createReactAppHandler', function () {
process.chdir(projectRoot)
const webpackConfig = createReactAppHandler({
devServerConfig: {
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig,
sourceWebpackModulesResult: { webpack: { majorVersion: 5 } } as SourceRelativeWebpackResult,
})
const { frameworkConfig: webpackConfig } = createReactAppHandler({
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
expect(webpackConfig.mode).eq('development')
expectEslintModifications(webpackConfig)
@@ -88,16 +82,13 @@ describe('createReactAppHandler', function () {
})
it('sources the config from ejected cra', async () => {
const projectRoot = await scaffoldMigrationProject('cra-5')
const projectRoot = await scaffoldMigrationProject('cra-ejected')
process.chdir(projectRoot)
const webpackConfig = createReactAppHandler({
devServerConfig: {
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig,
sourceWebpackModulesResult: { webpack: { majorVersion: 5 } } as SourceRelativeWebpackResult,
})
const { frameworkConfig: webpackConfig } = createReactAppHandler({
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
expect(webpackConfig.mode).eq('development')
expectEslintModifications(webpackConfig)

View File

@@ -0,0 +1,77 @@
import { scaffoldMigrationProject } from '../test-helpers/scaffoldProject'
import { expect } from 'chai'
import { nextHandler } from '../../src/helpers/nextHandler'
import type { Configuration } from 'webpack'
import * as path from 'path'
import { WebpackDevServerConfig } from '../../src/devServer'
import '../support'
const expectWatchOverrides = (webpackConfig: Configuration) => {
expect(webpackConfig.watchOptions.ignored).to.contain('**/node_modules/!(@cypress/webpack-dev-server/dist/browser.js)**')
}
const expectPagesDir = (webpackConfig: Configuration, projectRoot: string) => {
const ReactLoadablePlugin: any = webpackConfig.plugins.find((plugin) => plugin.constructor.name === 'ReactLoadablePlugin')
expect(ReactLoadablePlugin.pagesDir).eq(path.join(projectRoot, 'pages'))
}
const expectWebpackSpan = (webpackConfig: Configuration) => {
const ProfilingPlugin: any = webpackConfig.plugins.find((plugin) => plugin.constructor.name === 'ProfilingPlugin')
expect(ProfilingPlugin.runWebpackSpan).to.exist
}
describe('nextHandler', function () {
// can take a while since we install node_modules
this.timeout(1000 * 60)
it('sources from a next-12 project', async () => {
const projectRoot = await scaffoldMigrationProject('next-12')
process.chdir(projectRoot)
const { frameworkConfig: webpackConfig } = await nextHandler({
framework: 'next',
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
expectWatchOverrides(webpackConfig)
expectPagesDir(webpackConfig, projectRoot)
expectWebpackSpan(webpackConfig)
})
it('sources from a next-11 project', async () => {
const projectRoot = await scaffoldMigrationProject('next-11')
process.chdir(projectRoot)
const { frameworkConfig: webpackConfig } = await nextHandler({
framework: 'next',
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
expectWatchOverrides(webpackConfig)
expectPagesDir(webpackConfig, projectRoot)
expectWebpackSpan(webpackConfig)
})
it('throws if nodeVersion is set to bundled', async () => {
const projectRoot = await scaffoldMigrationProject('next-12')
process.chdir(projectRoot)
let err
try {
await nextHandler({
framework: 'next', cypressConfig: { projectRoot, nodeVersion: 'bundled' } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
} catch (e) {
err = e
}
expect(err.message).to.contain('Cypress cannot compile your Next.js application when "nodeVersion" is set to "bundled".')
})
})

View File

@@ -1,6 +1,8 @@
import { scaffoldMigrationProject } from '../test-helpers/scaffoldProject'
import { expect } from 'chai'
import { nuxtHandler } from '../../src/helpers/nuxtHandler'
import { WebpackDevServerConfig } from '../../src/devServer'
import '../support'
describe('nuxtHandler', function () {
// can take a while since we install node_modules
@@ -11,14 +13,12 @@ describe('nuxtHandler', function () {
process.chdir(projectRoot)
const config = await nuxtHandler({
devServerConfig: {
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
},
} as any)
const { frameworkConfig: webpackConfig } = await nuxtHandler({
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
// Verify it's a Vue-specific webpack config by seeing if VueLoader is present.
expect(config.plugins.find((plug) => plug.constructor.name === 'VueLoader'))
expect(config.performance).to.be.undefined
expect(webpackConfig.plugins.find((plug) => plug.constructor.name === 'VueLoader'))
expect(webpackConfig.performance).to.be.undefined
})
})

View File

@@ -1,6 +1,8 @@
import { scaffoldMigrationProject } from '../test-helpers/scaffoldProject'
import { expect } from 'chai'
import { vueCliHandler } from '../../src/helpers/vueCliHandler'
import { WebpackDevServerConfig } from '../../src/devServer'
import '../support'
describe('vueCliHandler', function () {
// can take a while since we install node_modules
@@ -11,14 +13,12 @@ describe('vueCliHandler', function () {
process.chdir(projectRoot)
const config = vueCliHandler({
devServerConfig: {
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
},
} as any)
const { frameworkConfig: webpackConfig } = vueCliHandler({
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
// Verify it's a Vue-specific webpack config by seeing if VueLoader is present.
expect(config.plugins.find((plug) => plug.constructor.name === 'VueLoader'))
expect(webpackConfig.plugins.find((plug) => plug.constructor.name === 'VueLoader'))
})
it('sources from a @vue/cli-service@4.x project with Vue 2', async () => {
@@ -26,13 +26,11 @@ describe('vueCliHandler', function () {
process.chdir(projectRoot)
const config = vueCliHandler({
devServerConfig: {
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
},
} as any)
const { frameworkConfig: webpackConfig } = vueCliHandler({
cypressConfig: { projectRoot } as Cypress.PluginConfigOptions,
} as WebpackDevServerConfig)
// Verify it's a Vue-specific webpack config by seeing if VueLoader is present.
expect(config.plugins.find((plug) => plug.constructor.name === 'VueLoader'))
expect(webpackConfig.plugins.find((plug) => plug.constructor.name === 'VueLoader'))
})
})

View File

@@ -2,7 +2,7 @@ import { expect } from 'chai'
import EventEmitter from 'events'
import snapshot from 'snap-shot-it'
import { WebpackDevServerConfig } from '../src/devServer'
import { sourceRelativeWebpackModules } from '../src/helpers/sourceRelativeWebpackModules'
import { sourceDefaultWebpackDependencies } from '../src/helpers/sourceRelativeWebpackModules'
import { makeWebpackConfig } from '../src/makeWebpackConfig'
describe('makeWebpackConfig', () => {
@@ -24,7 +24,7 @@ describe('makeWebpackConfig', () => {
}
const actual = await makeWebpackConfig({
devServerConfig,
sourceWebpackModulesResult: sourceRelativeWebpackModules(devServerConfig),
sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig),
})
// plugins contain circular deps which cannot be serialized in a snapshot.

View File

@@ -5,8 +5,9 @@ import { expect } from 'chai'
import path from 'path'
import fs from 'fs'
import { restoreLoadHook, sourceRelativeWebpackModules } from '../src/helpers/sourceRelativeWebpackModules'
import { sourceDefaultWebpackDependencies } from '../src/helpers/sourceRelativeWebpackModules'
import { WebpackDevServerConfig } from '../src/devServer'
import './support'
type ProjectDirs = typeof fixtureDirs
@@ -45,7 +46,7 @@ async function sourceModulesForProject (fixture: ProjectDirs[number]) {
await FixturesScaffold.scaffoldProjectNodeModules(fixture)
const result = sourceRelativeWebpackModules({
const result = sourceDefaultWebpackDependencies({
cypressConfig: {
projectRoot,
},
@@ -56,16 +57,7 @@ async function sourceModulesForProject (fixture: ProjectDirs[number]) {
// Ensures that we are properly sourcing the webpacks from the node_modules in the given project,
// rather than from the node_modules in the project root
describe('sourceRelativeWebpackModules', () => {
beforeEach(() => {
delete require.cache
restoreLoadHook()
})
after(() => {
restoreLoadHook()
})
describe('sourceDefaultWebpackDependencies', () => {
for (const [fixture, versionsToMatch] of Object.entries(WEBPACK_REACT)) {
describe(fixture, () => {
it(`sources the correct webpack versions for ${fixture}`, async () => {

View File

@@ -0,0 +1,10 @@
import { restoreLoadHook } from '../src/helpers/sourceRelativeWebpackModules'
beforeEach(() => {
delete require.cache
restoreLoadHook()
})
after(() => {
restoreLoadHook()
})

View File

@@ -0,0 +1,18 @@
const path = require('path');
module.exports = {
component: {
devServer: {
framework: 'next',
bundler: 'webpack',
// Necessary due to cypress/react resolving from cypress/node_modules rather than the project root
webpackConfig: {
resolve: {
alias: {
'react': path.resolve(__dirname, './node_modules/react'),
}
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -0,0 +1 @@
//

View File

@@ -0,0 +1,7 @@
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

View File

@@ -0,0 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}

View File

@@ -0,0 +1,9 @@
import { mount } from "cypress/react"
import Index from "./index.js"
describe('<Index />', () => {
it('renders', () => {
mount(<Index />)
cy.contains('h1', 'Welcome to Next.js!')
})
})

View File

@@ -0,0 +1,69 @@
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.js</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h2>Documentation &rarr;</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h2>Learn &rarr;</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h2>Deploy &rarr;</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,121 @@
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.footer {
width: 100%;
height: 100px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
}
.footer a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
line-height: 1.5;
font-size: 1.5rem;
}
.code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
.card {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
width: 45%;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
margin-left: 0.5rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}

View File

@@ -0,0 +1,16 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}

View File

@@ -0,0 +1,3 @@
module.exports = {
webpack5: false,
}

View File

@@ -0,0 +1,17 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^11.0.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"projectFixtureDirectory": "next"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^11.0.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"projectFixtureDirectory": "next"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "12.1.4",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"projectFixtureDirectory": "next"
}

View File

@@ -0,0 +1,167 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@next/env@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.4.tgz#5af629b43075281ecd7f87938802b7cf5b67e94b"
integrity sha512-7gQwotJDKnfMxxXd8xJ2vsX5AzyDxO3zou0+QOXX8/unypA6icw5+wf6A62yKZ6qQ4UZHHxS68pb6UV+wNneXg==
"@next/swc-android-arm-eabi@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.1.4.tgz#c3dae178b7c15ad627d2e9b8dfb38caecb5c4ac7"
integrity sha512-FJg/6a3s2YrUaqZ+/DJZzeZqfxbbWrynQMT1C5wlIEq9aDLXCFpPM/PiOyJh0ahxc0XPmi6uo38Poq+GJTuKWw==
"@next/swc-android-arm64@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.4.tgz#f320d60639e19ecffa1f9034829f2d95502a9a51"
integrity sha512-LXraazvQQFBgxIg3Htny6G5V5he9EK7oS4jWtMdTGIikmD/OGByOv8ZjLuVLZLtVm3UIvaAiGtlQSLecxJoJDw==
"@next/swc-darwin-arm64@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.4.tgz#fd578278312613eddcf3aee26910100509941b63"
integrity sha512-SSST/dBymecllZxcqTCcSTCu5o1NKk9I+xcvhn/O9nH6GWjgvGgGkNqLbCarCa0jJ1ukvlBA138FagyrmZ/4rQ==
"@next/swc-darwin-x64@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.4.tgz#ace5f80d8c8348efe194f6d7074c6213c52b3944"
integrity sha512-p1lwdX0TVjaoDXQVuAkjtxVBbCL/urgxiMCBwuPDO7TikpXtSRivi+mIzBj5q7ypgICFmIAOW3TyupXeoPRAnA==
"@next/swc-linux-arm-gnueabihf@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.4.tgz#2bf2c83863635f19c71c226a2df936e001cce29c"
integrity sha512-67PZlgkCn3TDxacdVft0xqDCL7Io1/C4xbAs0+oSQ0xzp6OzN2RNpuKjHJrJgKd0DsE1XZ9sCP27Qv0591yfyg==
"@next/swc-linux-arm64-gnu@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.4.tgz#d577190f641c9b4b463719dd6b8953b6ba9be8d9"
integrity sha512-OnOWixhhw7aU22TQdQLYrgpgFq0oA1wGgnjAiHJ+St7MLj82KTDyM9UcymAMbGYy6nG/TFOOHdTmRMtCRNOw0g==
"@next/swc-linux-arm64-musl@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.4.tgz#e70ffe70393d8f9242deecdb282ce5a8fd588b14"
integrity sha512-UoRMzPZnsAavdWtVylYxH8DNC7Uy0i6RrvNwT4PyQVdfANBn2omsUkcH5lgS2O7oaz0nAYLk1vqyZDO7+tJotA==
"@next/swc-linux-x64-gnu@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.4.tgz#91498a130387fb1961902f2bee55863f8e910cff"
integrity sha512-nM+MA/frxlTLUKLJKorctdI20/ugfHRjVEEkcLp/58LGG7slNaP1E5d5dRA1yX6ISjPcQAkywas5VlGCg+uTvA==
"@next/swc-linux-x64-musl@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.4.tgz#78057b03c148c121553d41521ad38f6c732762ff"
integrity sha512-GoRHxkuW4u4yKw734B9SzxJwVdyEJosaZ62P7ifOwcujTxhgBt3y76V2nNUrsSuopcKI2ZTDjaa+2wd5zyeXbA==
"@next/swc-win32-arm64-msvc@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.4.tgz#05bbaabacac23b8edf6caa99eb86b17550a09051"
integrity sha512-6TQkQze0ievXwHJcVUrIULwCYVe3ccX6T0JgZ1SiMeXpHxISN7VJF/O8uSCw1JvXZYZ6ud0CJ7nfC5HXivgfPg==
"@next/swc-win32-ia32-msvc@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.4.tgz#8fd2fb48f04a2802e51fc320878bf6b411c1c866"
integrity sha512-CsbX/IXuZ5VSmWCpSetG2HD6VO5FTsO39WNp2IR2Ut/uom9XtLDJAZqjQEnbUTLGHuwDKFjrIO3LkhtROXLE/g==
"@next/swc-win32-x64-msvc@12.1.4":
version "12.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.4.tgz#a72ed44c9b1f850986a30fe36c59e01f8a79b5f3"
integrity sha512-JtYuWzKXKLDMgE/xTcFtCm1MiCIRaAc5XYZfYX3n/ZWSI1SJS/GMm+Su0SAHJgRFavJh6U/p998YwO/iGTIgqQ==
caniuse-lite@^1.0.30001283:
version "1.0.30001328"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz#0ed7a2ca65ec45872c613630201644237ba1e329"
integrity sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
nanoid@^3.1.30:
version "3.3.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557"
integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==
next@12.1.4:
version "12.1.4"
resolved "https://registry.yarnpkg.com/next/-/next-12.1.4.tgz#597a9bdec7aec778b442c4f6d41afd2c64a54b23"
integrity sha512-DA4g97BM4Z0nKtDvCTm58RxdvoQyYzeg0AeVbh0N4Y/D8ELrNu47lQeEgRGF8hV4eQ+Sal90zxrJQQG/mPQ8CQ==
dependencies:
"@next/env" "12.1.4"
caniuse-lite "^1.0.30001283"
postcss "8.4.5"
styled-jsx "5.0.1"
optionalDependencies:
"@next/swc-android-arm-eabi" "12.1.4"
"@next/swc-android-arm64" "12.1.4"
"@next/swc-darwin-arm64" "12.1.4"
"@next/swc-darwin-x64" "12.1.4"
"@next/swc-linux-arm-gnueabihf" "12.1.4"
"@next/swc-linux-arm64-gnu" "12.1.4"
"@next/swc-linux-arm64-musl" "12.1.4"
"@next/swc-linux-x64-gnu" "12.1.4"
"@next/swc-linux-x64-musl" "12.1.4"
"@next/swc-win32-arm64-msvc" "12.1.4"
"@next/swc-win32-ia32-msvc" "12.1.4"
"@next/swc-win32-x64-msvc" "12.1.4"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@8.4.5:
version "8.4.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==
dependencies:
nanoid "^3.1.30"
picocolors "^1.0.0"
source-map-js "^1.0.1"
react-dom@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
source-map-js@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
styled-jsx@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.1.tgz#78fecbbad2bf95ce6cd981a08918ce4696f5fc80"
integrity sha512-+PIZ/6Uk40mphiQJJI1202b+/dYeTVd9ZnMPR80pgiWbjIwvN2zIp4r9et0BgqBuShh48I0gttPlAXA7WVvBxw==