加快 Gatsby 性能

Posted

技术标签:

【中文标题】加快 Gatsby 性能【英文标题】:Speed Up Gatsby Performance 【发布时间】:2020-05-20 17:46:51 【问题描述】:

我在工作中被分配了一项任务,以提高我的项目的性能。目前,Google Lighthouse 的分数在波动,但总体而言分数并不高,因此我们正在努力弄清楚如何提高其性能,以便能够向我们的领导者炫耀。

我们的项目将整个 Gatsby 网站加载为单个 javascript 包。这会从站点创建一个单页应用程序,它允许通过 JavaScript 快速加载新页面。但是对于像我们的 WordPress 网站这样大的东西,这会产生以兆字节为单位的非常大的捆绑包。这个大包显着降低了页面速度。

我不完全确定如何卸载这个 bundle.js,但我发现了一个关于该主题的有趣文档 https://www.gatsbyjs.org/docs/how-code-splitting-works/

虽然我还没有完全理解这些文档,但我相信我编辑了这个 async-requires.js 文件以包含多个导出组件行,这应该会导致多个 javascript 包而不是主要的大型包。也许如果有多个 js 包,网站加载速度会更快,因为它不仅仅是一个瓶颈。因此,页面可以加载到它需要渲染的特定包中,并异步加载它不需要的包。

以下是我认为与手头任务相关的一些代码。在 gatsby 方面,我还是个初学者,所以我不确定我可以在此处进行哪些更改以提高性能。

感谢您的帮助。

async-requires.js

const preferDefault = m => m && m.default || m

exports.components = 
  "component---src-templates-page-js": () => import("../src/templates/page.js" /* webpackChunkName: "component---src-templates-page-js" */),
  "component---cache-dev-404-page-js": () => import("dev-404-page.js" /* webpackChunkName: "component---cache-dev-404-page-js" */),
  "component---src-pages-404-js": () => import("../src/pages/404.js" /* webpackChunkName: "component---src-pages-404-js" */)


src/templates/pages.js

import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import Layout from '../layouts/layout'
import AnalyticsContext,  analyticsEvents  from '../../util/AnalyticsContext'

import Banner from '../WPComponents/Banner'
import CheckmarkList from '../WPComponents/CheckmarkList'
import CopyGrid from '../WPComponents/CopyGrid'
import Drawers from '../WPComponents/Drawers'
import Explainers from '../WPComponents/Explainers'
import Featured from '../WPComponents/Featured'
import Form from '../WPComponents/Form'
import Hero from '../WPComponents/Hero'
import Pricing from '../WPComponents/Pricing'
import PromoApp from '../WPComponents/PromoApp'
import PromoCircles from '../WPComponents/PromoCircles'
import PromoSlider from '../WPComponents/PromoSlider'
import ReachAnimation from '../WPComponents/ReachAnimation'
import Resources from '../WPComponents/Resources'
import SimpleExplainer from '../WPComponents/SimpleExplainer'
import SimpleMedia from '../WPComponents/SimpleMedia'
import Solution from '../WPComponents/Solution'
import Testimonials from '../WPComponents/Testimonials'
import Disclaimer from '../WPComponents/Disclaimer'

const PageTemplate = props => 
  const  pageContext, data, location  = props
  const components = (pageContext.acf && pageContext.acf.section_page) || []
  let helmet
  const  yoast  = pageContext

  if (yoast) 
    const 
      title,
      metadesc,
      opengraph_title,
      opengraph_description,
      opengraph_image,
      canonical,
     = yoast

    helmet = (
      <Helmet
        title=title || ' '
        meta=[
          
            name: 'robots',
            content: 'noindex',
          ,
          
            name: 'description',
            content: metadesc || ' ',
          ,
          
            property: 'og:title',
            content: opengraph_title || ' ',
          ,
           property: 'og:site_name', content: title || ' ' ,
           property: 'og:type', content: 'website' ,
          
            property: 'og:description',
            content: opengraph_description || ' ',
          ,
          
            property: 'og:image',
            content: opengraph_image && opengraph_image.source_url,
          ,
          canonical
            ? 
                property: 'og:url',
                content: canonical || ' ',
              
            : ,
        ]
      />
    )
  

  return (
    <AnalyticsContext.Provider
      value=
        ...analyticsEvents,
      
    >
      <Layout location=location>
        helmet
        components.map(component => 
          switch (component.__typename) 
            case 'WordPressAcf_hero':
              return <Hero key=component.id ...component />
            case 'WordPressAcf_featured':
              return <Featured key=component.id ...component />
            case 'WordPressAcf_solution':
              return <Solution key=component.id ...component />
            case 'WordPressAcf_resources':
              return <Resources key=component.id ...component />
            case 'WordPressAcf_simplemedia':
              return <SimpleMedia key=component.id ...component />
            case 'WordPressAcf_promoapp':
              return <PromoApp key=component.id ...component />
            case 'WordPressAcf_reach_animation':
              return <ReachAnimation key=component.id ...component />
            case 'WordPressAcf_promoslider':
              return <PromoSlider key=component.id ...component />
            case 'WordPressAcf_promocircles':
              return <PromoCircles key=component.id ...component />
            case 'WordPressAcf_testimonials':
              return <Testimonials key=component.id ...component />
            case 'WordPressAcf_banner':
              return <Banner key=component.id ...component />
            case 'WordPressAcf_explainers':
              return <Explainers key=component.id ...component />
            case 'WordPressAcf_copygrid':
              return <CopyGrid key=component.id ...component />
            case 'WordPressAcf_drawers':
              return <Drawers key=component.id ...component />
            case 'WordPressAcf_simpleexplainer':
              return <SimpleExplainer key=component.id ...component />
            case 'WordPressAcf_disclaimer':
              return <Disclaimer key=component.id ...component />
            case 'WordPressAcf_pricing':
              return (
                <Pricing key=component.id ...component /> 
              )
            case 'WordPressAcf_checkmarklist':
              return <CheckmarkList key=component.id ...component />
            case 'WordPressAcf_form':
              return <Form key=component.id ...component />
            default:
              console.log('Could not recongize type:', component.__typename)
              return
          
        )
      </Layout>
    </AnalyticsContext.Provider>
  )


PageTemplate.propTypes = 
  pageContext: PropTypes.shape(
    acf: PropTypes.object,
    media: PropTypes.shape(
      edges: PropTypes.array,
    ),
  ),


export default PageTemplate

pageCreators.js

const path = require('path')
const genericPageTemplate = 'src/templates/page.js'

const pageCreator = templatePath => (actions, pageContext) => 

  actions.createPage(
    component: path.resolve(templatePath),
    path: pageContext.pagePath,
    context: 
      ...pageContext,
    ,
  )


module.exports = 
  createGenericPage: pageCreator(genericPageTemplate),

createPages.js

const  createGenericPage  = require('./pageCreators')

const generatePages = allWordpressPage => 
  return allWordpressPage.edges.map(edge => edge.node)


module.exports = (data, actions) => 
  if (!data) 
    console.error('createPages()', 'Error', '`data` is undefined')
    throw new Error('Error retrieving data: data is undefined')
  

  const  allWordpressPage  = data

  const pages = allWordpressPage && generatePages(allWordpressPage)

  if (!pages) 
    console.error(
      'createPages()',
      'Error',
      'Could not build pages. allWordpressPage was falsy'
    )
    throw new Error('Error retreiving data: allWordpressPage was falsy')
  

  pages &&
    pages.forEach(page => 

      // skip the 'modules' page
      if (page.pagePath === '/modules/') 
        return;
      

      createGenericPage(actions, page)
    )

gatsby-node.js

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */

const fs = require('fs')

const queryAll = require('./util/queryAll')
const createPages = require('./util/createPages')

exports.createPages = ( graphql, actions ) => 
  return graphql(queryAll)
    .then(res => 
      if (res.errors) 
        res.errors.forEach(error => 
          console.error('Error:', error.message)
        )
      

      createPages(res.data, actions)
    )
    .catch(error => 
      console.error('failed to create pages:',  error )
    )


exports.sourceNodes = ( actions, schema ) => 
  const  createTypes  = actions

  const additionalTypeDefs = fs.readFileSync(`type-defs.gql`, 
    encoding: `utf-8`,
  )
  createTypes(additionalTypeDefs)


// temporary fix for dev env: https://github.com/gatsbyjs/gatsby/issues/11934#issuecomment-469046186
exports.onCreateWebpackConfig = ( getConfig, stage ) => 
  const config = getConfig()
  if (stage.startsWith('develop') && config.resolve) 
    config.resolve.alias = 
      ...config.resolve.alias,
      'react-dom': '@hot-loader/react-dom',
    
  

【问题讨论】:

您介意分享一下developers.google.com/speed/pagespeed/insights 对网站的评价吗? @PatrikRikama-Hinnenberg developers.google.com/speed/pagespeed/insights/… 【参考方案1】:

大约 4 个月前,我对此进行了很多研究,这就是我发现的,但下面的一些原因是由于 lighthouse 如何确定 Gatsby 网站上的页面速度存在错误,所以有些可能不再正确(例如,在图像上使用fadeIn=falseloading="eager",并使用a 标签而不是gatsby-link 中的Link。如果这些提示之一不再适用,请发表评论或编辑。


使用gatsby-plugin-preact(大而简单的更改)

使用&lt;a&gt; 标签而不是gatsby-link(现在很可能已修复)

使用gatsby-plugin-purge-css(删除所有未使用的 CSS。如果您使用的是引导程序等 CSS 框架,则很有用)

在 Gatsby 图像上使用 fadeIn=falseloading="eager",或者将淡入的持续时间设置为更低:durationFadeIn=250

使用gatsby-plugin-preconnect预连接到某些第 3 方网站

如果您有背景图片,请将其拆分为 2 张图片,一张用于首屏,一张用于首屏(您的页面初始视图必须在开始时加载较少)

在让 gatsby 优化它们之前手动优化我的“首屏”图像。 This was a website 我发现这样做很有帮助,但你也许可以找到一个好的开源软件。

仅在用户滚动经过第三方 iframe 后才加载它们。例如:

   ...
   const ref = useRef()
   const onScreen = useOnScreen(ref, '0px')
   let showWidget
   if (onScreen)
       showWidget = true
   
   ...
   return (
       <div ref=ref>
           showWidget && <ThirdPartyIframe /> 
       </div>
   )

我读过的其他提示包括

Using inline styling(虽然I've heard Gatsby does this automatically)

Using a 301 redirect instead of 307(如果这适用于您)

不使用Typography.js

Possibly using an S3 & Cloudfront and not Netlify to host the website


进一步阅读的资源

I created a reddit post 我在其中发布了类似的内容,我建议阅读下面的 cmets。 It references this github thread which is pretty popular,我发现 this post 是该主题中最有帮助的。

此外,我还发布了一些与提高 Gatsby 项目的灯塔分数相关的问题。上面列出的信息你不应该需要它们,但也许它们会很有用,或者你会从它们身上学到一些东西

Largest contententful paint (LCP) on lighthouse is a p tag. (Using gatsby) Pagespeed insights avoid chaining critical request only lists Initial navigation followed by my domain twice Reduce initial server response time with Netlify and Gatsby Largest contentful paint is one big gatsby-background-image and very slow, PageSpeed Insights tells me to preconnect third-party origins and then tells me they are not used by the browser after preconnecting PageSpeed Insights diagnostic “Ensure text remains visible during webfont load” Load third party iframe in React after page load, so that the iframe does not affect PageSpeed score

【讨论】:

以上是关于加快 Gatsby 性能的主要内容,如果未能解决你的问题,请参考以下文章

Gatsby:graphql 查询中的 gatsby-source-graphql 和 gatsby-plugin-sharp

MDX 中的 Gatsby 静态图像(gatsby-plugin-image)

gatsby-image-plugin,StaticImage 不能覆盖默认的包装样式(gatsby-image-wrapper & gatsby-image-wrapper-constrai

Gatsby - webpack 无法使用 `gatsby-plugin-alias-imports` 解析别名导入

带有 gatsby 图像的 Airtable gatsby 源插件

在 Gatsby V4 中使用 Gatsby Source Contentful 找不到缓存文件