SPA模式下的es6如何加快宿主页的显示速度

SPA的模式下,宿主页是首先加载的页面,会需要一些主要的组件,如element-plus,easyui,devextreme,ant-design等,这些组件及其依赖组件,文件多,代码量大,可能导致首页加载很慢,超过3-8秒,必须优化。文件多,浏览器与服务器之间的交互次数多,网络来回多,浪费了大量时间,如果能一次打包返回,可以提高效率。

但是SPA,不像MPA,有很多差异:

(1)SPA后续加载的作业组件,对公共组件都是内存共用的。而MPA每个页面可以不考虑共用,单独打包或tree-shaking精简。因此SPA下不能使用webpack打包模式来解决这个问题。

(2)组件的引用方式必须统一。要么大家都是引用其打包文件(如:/lodash/lodash.js),要么大家都引用零散文件(如:/lodash/目录下640个文件)。如果混用,代码在内存就不是一致的,可能引起其逻辑错误。

第三方组件,很多小文件:

  • devextreme,devextreme-vue,700+
  • lodash-es,640+
  • element-plus,360+
  • ant-design-vue,530+

在es6模式下,这些第三方组件一般没有提供一个打包后的大文件,一般官方建议分散使用。但它们通常又提供了一个index.mjs文件,在里面一网打尽的引用了全部小文件。如element-plus/es/index.mjs

复制代码
import installer from './defaults.mjs';
export { default } from './defaults.mjs';
import './components/index.mjs';
import './constants/index.mjs';
import './directives/index.mjs';
import './hooks/index.mjs';
export { makeInstaller } from './make-installer.mjs';
export { default as dayjs } from 'dayjs';
export { affixEmits, affixProps } from './components/affix/src/affix.mjs';
export { ElAffix } from './components/affix/index.mjs';
export { alertEffects, alertEmits, alertProps } from './components/alert/src/alert.mjs';
export { ElAlert } from './components/alert/index.mjs';
export { autocompleteEmits, autocompleteProps } from './components/autocomplete/src/autocomplete.mjs';
export { ElAutocomplete } from './components/autocomplete/index.mjs';
export { avatarEmits, avatarProps } from './components/avatar/src/avatar.mjs';
export { ElAvatar } from './components/avatar/index.mjs';
export { backtopEmits, backtopProps } from './components/backtop/src/backtop.mjs';
export { ElBacktop } from './components/backtop/index.mjs';
export { badgeProps } from './components/badge/src/badge.mjs';
export { ElBadge } from './components/badge/index.mjs';
export { breadcrumbProps } from './components/breadcrumb/src/breadcrumb.mjs';
export { breadcrumbItemProps } from './components/breadcrumb/src/breadcrumb-item.mjs';
export { breadcrumbKey } from './components/breadcrumb/src/constants.mjs';
export { ElBreadcrumb, ElBreadcrumbItem } from './components/breadcrumb/index.mjs';
export { buttonEmits, buttonNativeTypes, buttonProps, buttonTypes } from './components/button/src/button.mjs';
export { buttonGroupContextKey } from './components/button/src/constants.mjs';
export { ElButton, ElButtonGroup } from './components/button/index.mjs';
export { calendarEmits, calendarProps } from './components/calendar/src/calendar.mjs';
export { ElCalendar } from './components/calendar/index.mjs';
export { cardProps } from './components/card/src/card.mjs';
export { ElCard } from './components/card/index.mjs';
export { carouselEmits, carouselProps } from './components/carousel/src/carousel.mjs';
export { carouselItemProps } from './components/carousel/src/carousel-item.mjs';
export { CAROUSEL_ITEM_NAME, carouselContextKey } from './components/carousel/src/constants.mjs';
export { ElCarousel, ElCarouselItem } from './components/carousel/index.mjs';
export { cascaderEmits, cascaderProps } from './components/cascader/src/cascader.mjs';
export { ElCascader } from './components/cascader/index.mjs';
export { CASCADER_PANEL_INJECTION_KEY } from './components/cascader-panel/src/types.mjs';
export { CommonProps, DefaultProps, useCascaderConfig } from './components/cascader-panel/src/config.mjs';
export { ElCascaderPanel } from './components/cascader-panel/index.mjs';
export { checkTagEmits, checkTagProps } from './components/check-tag/src/check-tag.mjs';
export { ElCheckTag } from './components/check-tag/index.mjs';
export { checkboxGroupEmits, checkboxGroupProps } from './components/checkbox/src/checkbox-group.mjs';
export { checkboxEmits, checkboxProps } from './components/checkbox/src/checkbox.mjs';
export { checkboxGroupContextKey } from './components/checkbox/src/constants.mjs';
export { ElCheckbox, ElCheckboxButton, ElCheckboxGroup } from './components/checkbox/index.mjs';
export { colProps } from './components/col/src/col.mjs';
export { ElCol } from './components/col/index.mjs';
export { collapseEmits, collapseProps, emitChangeFn } from './components/collapse/src/collapse.mjs';
export { collapseItemProps } from './components/collapse/src/collapse-item.mjs';
export { collapseContextKey } from './components/collapse/src/constants.mjs';
export { ElCollapse, ElCollapseItem } from './components/collapse/index.mjs';
export { ElCollapseTransition } from './components/collapse-transition/index.mjs';
export { colorPickerContextKey, colorPickerEmits, colorPickerProps } from './components/color-picker/src/color-picker.mjs';
export { ElColorPicker } from './components/color-picker/index.mjs';
export { messageConfig } from './components/config-provider/src/config-provider.mjs';
export { configProviderProps } from './components/config-provider/src/config-provider-props.mjs';
export { configProviderContextKey } from './components/config-provider/src/constants.mjs';
export { provideGlobalConfig, useGlobalComponentSettings, useGlobalConfig } from './components/config-provider/src/hooks/use-global-config.mjs';
export { ElConfigProvider } from './components/config-provider/index.mjs';
export { ElAside, ElContainer, ElFooter, ElHeader, ElMain } from './components/container/index.mjs';
export { countdownEmits, countdownProps } from './components/countdown/src/countdown.mjs';
export { ElCountdown } from './components/countdown/index.mjs';
export { ROOT_PICKER_INJECTION_KEY } from './components/date-picker/src/constants.mjs';
export { datePickerProps } from './components/date-picker/src/props/date-picker.mjs';
export { ElDatePicker } from './components/date-picker/index.mjs';
export { descriptionProps } from './components/descriptions/src/description.mjs';
export { descriptionItemProps } from './components/descriptions/src/description-item.mjs';
export { ElDescriptions, ElDescriptionsItem } from './components/descriptions/index.mjs';
export { useDialog } from './components/dialog/src/use-dialog.mjs';
export { dialogEmits, dialogProps } from './components/dialog/src/dialog.mjs';
export { dialogInjectionKey } from './components/dialog/src/constants.mjs';
export { ElDialog } from './components/dialog/index.mjs';
export { dividerProps } from './components/divider/src/divider.mjs';
export { ElDivider } from './components/divider/index.mjs';
export { drawerEmits, drawerProps } from './components/drawer/src/drawer.mjs';
export { ElDrawer } from './components/drawer/index.mjs';
export { DROPDOWN_COLLECTION_INJECTION_KEY, DROPDOWN_COLLECTION_ITEM_INJECTION_KEY, ElCollection, ElCollectionItem, FIRST_KEYS, FIRST_LAST_KEYS, LAST_KEYS, dropdownItemProps, dropdownMenuProps, dropdownProps } from './components/dropdown/src/dropdown.mjs';
export { DROPDOWN_INJECTION_KEY } from './components/dropdown/src/tokens.mjs';
export { ElDropdown, ElDropdownItem, ElDropdownMenu } from './components/dropdown/index.mjs';
export { emptyProps } from './components/empty/src/empty.mjs';
export { ElEmpty } from './components/empty/index.mjs';
export { formEmits, formMetaProps, formProps } from './components/form/src/form.mjs';
export { formItemProps, formItemValidateStates } from './components/form/src/form-item.mjs';
export { formContextKey, formItemContextKey } from './components/form/src/constants.mjs';
export { useDisabled, useFormDisabled, useFormSize, useSize } from './components/form/src/hooks/use-form-common-props.mjs';
export { useFormItem, useFormItemInputId } from './components/form/src/hooks/use-form-item.mjs';
export { ElForm, ElFormItem } from './components/form/index.mjs';
export { iconProps } from './components/icon/src/icon.mjs';
export { ElIcon } from './components/icon/index.mjs';
export { imageEmits, imageProps } from './components/image/src/image.mjs';
export { ElImage } from './components/image/index.mjs';
export { imageViewerEmits, imageViewerProps } from './components/image-viewer/src/image-viewer.mjs';
export { ElImageViewer } from './components/image-viewer/index.mjs';
export { inputEmits, inputProps } from './components/input/src/input.mjs';
export { ElInput } from './components/input/index.mjs';
export { inputNumberEmits, inputNumberProps } from './components/input-number/src/input-number.mjs';
export { ElInputNumber } from './components/input-number/index.mjs';
export { linkEmits, linkProps } from './components/link/src/link.mjs';
export { ElLink } from './components/link/index.mjs';
export { menuEmits, menuProps } from './components/menu/src/menu.mjs';
export { menuItemEmits, menuItemProps } from './components/menu/src/menu-item.mjs';
export { menuItemGroupProps } from './components/menu/src/menu-item-group.mjs';
export { subMenuProps } from './components/menu/src/sub-menu.mjs';
export { ElMenu, ElMenuItem, ElMenuItemGroup, ElSubMenu } from './components/menu/index.mjs';
export { overlayEmits, overlayProps } from './components/overlay/src/overlay.mjs';
export { ElOverlay } from './components/overlay/index.mjs';
export { pageHeaderEmits, pageHeaderProps } from './components/page-header/src/page-header.mjs';
export { ElPageHeader } from './components/page-header/index.mjs';
export { paginationEmits, paginationProps } from './components/pagination/src/pagination.mjs';
export { elPaginationKey } from './components/pagination/src/constants.mjs';
export { ElPagination } from './components/pagination/index.mjs';
export { popconfirmEmits, popconfirmProps } from './components/popconfirm/src/popconfirm.mjs';
export { ElPopconfirm } from './components/popconfirm/index.mjs';
export { Effect, popperProps, roleTypes, usePopperProps } from './components/popper/src/popper.mjs';
export { popperTriggerProps, usePopperTriggerProps } from './components/popper/src/trigger.mjs';
export { popperContentEmits, popperContentProps, popperCoreConfigProps, usePopperContentEmits, usePopperContentProps, usePopperCoreConfigProps } from './components/popper/src/content.mjs';
export { popperArrowProps, usePopperArrowProps } from './components/popper/src/arrow.mjs';
export { POPPER_CONTENT_INJECTION_KEY, POPPER_INJECTION_KEY } from './components/popper/src/constants.mjs';
export { default as ElPopperArrow } from './components/popper/src/arrow2.mjs';
export { default as ElPopperTrigger } from './components/popper/src/trigger2.mjs';
export { default as ElPopperContent } from './components/popper/src/content2.mjs';
export { ElPopper } from './components/popper/index.mjs';
export { progressProps } from './components/progress/src/progress.mjs';
export { ElProgress } from './components/progress/index.mjs';
export { radioEmits, radioProps, radioPropsBase } from './components/radio/src/radio.mjs';
export { radioGroupEmits, radioGroupProps } from './components/radio/src/radio-group.mjs';
export { radioButtonProps } from './components/radio/src/radio-button.mjs';
export { radioGroupKey } from './components/radio/src/constants.mjs';
export { ElRadio, ElRadioButton, ElRadioGroup } from './components/radio/index.mjs';
export { rateEmits, rateProps } from './components/rate/src/rate.mjs';
export { ElRate } from './components/rate/index.mjs';
export { IconComponentMap, IconMap, resultProps } from './components/result/src/result.mjs';
export { ElResult } from './components/result/index.mjs';
export { RowAlign, RowJustify, rowProps } from './components/row/src/row.mjs';
export { rowContextKey } from './components/row/src/constants.mjs';
export { ElRow } from './components/row/index.mjs';
export { BAR_MAP, GAP, renderThumbStyle } from './components/scrollbar/src/util.mjs';
export { scrollbarEmits, scrollbarProps } from './components/scrollbar/src/scrollbar.mjs';
export { thumbProps } from './components/scrollbar/src/thumb.mjs';
export { scrollbarContextKey } from './components/scrollbar/src/constants.mjs';
export { ElScrollbar } from './components/scrollbar/index.mjs';
export { selectGroupKey, selectKey } from './components/select/src/token.mjs';
export { ElOption, ElOptionGroup, ElSelect } from './components/select/index.mjs';
export { selectV2InjectionKey } from './components/select-v2/src/token.mjs';
export { ElSelectV2 } from './components/select-v2/index.mjs';
export { skeletonProps } from './components/skeleton/src/skeleton.mjs';
export { skeletonItemProps } from './components/skeleton/src/skeleton-item.mjs';
export { ElSkeleton, ElSkeletonItem } from './components/skeleton/index.mjs';
export { sliderEmits, sliderProps } from './components/slider/src/slider.mjs';
export { sliderContextKey } from './components/slider/src/constants.mjs';
export { ElSlider } from './components/slider/index.mjs';
export { spaceProps } from './components/space/src/space.mjs';
export { spaceItemProps } from './components/space/src/item.mjs';
export { useSpace } from './components/space/src/use-space.mjs';
export { ElSpace } from './components/space/index.mjs';
export { statisticProps } from './components/statistic/src/statistic.mjs';
export { ElStatistic } from './components/statistic/index.mjs';
export { stepProps } from './components/steps/src/item.mjs';
export { stepsEmits, stepsProps } from './components/steps/src/steps.mjs';
export { ElStep, ElSteps } from './components/steps/index.mjs';
export { switchEmits, switchProps } from './components/switch/src/switch.mjs';
export { ElSwitch } from './components/switch/index.mjs';
export { ElTable, ElTableColumn } from './components/table/index.mjs';
export { Alignment as TableV2Alignment, FixedDir as TableV2FixedDir, SortOrder as TableV2SortOrder } from './components/table-v2/src/constants.mjs';
export { default as TableV2 } from './components/table-v2/src/table-v2.mjs';
export { placeholderSign as TableV2Placeholder } from './components/table-v2/src/private.mjs';
export { autoResizerProps } from './components/table-v2/src/auto-resizer.mjs';
export { tableV2Props } from './components/table-v2/src/table.mjs';
export { tableV2RowProps } from './components/table-v2/src/row.mjs';
export { ElAutoResizer, ElTableV2 } from './components/table-v2/index.mjs';
export { tabsEmits, tabsProps } from './components/tabs/src/tabs.mjs';
export { tabBarProps } from './components/tabs/src/tab-bar.mjs';
export { tabNavEmits, tabNavProps } from './components/tabs/src/tab-nav.mjs';
export { tabPaneProps } from './components/tabs/src/tab-pane.mjs';
export { tabsRootContextKey } from './components/tabs/src/constants.mjs';
export { ElTabPane, ElTabs } from './components/tabs/index.mjs';
export { tagEmits, tagProps } from './components/tag/src/tag.mjs';
export { ElTag } from './components/tag/index.mjs';
export { textProps } from './components/text/src/text.mjs';
export { ElText } from './components/text/index.mjs';
export { buildTimeList, dateEquals, extractDateFormat, extractTimeFormat, formatter, makeList, parseDate, rangeArr, valueEquals } from './components/time-picker/src/utils.mjs';
export { DEFAULT_FORMATS_DATE, DEFAULT_FORMATS_DATEPICKER, DEFAULT_FORMATS_TIME, timeUnits } from './components/time-picker/src/constants.mjs';
export { timePickerDefaultProps } from './components/time-picker/src/common/props.mjs';
export { ElTimePicker } from './components/time-picker/index.mjs';
export { default as CommonPicker } from './components/time-picker/src/common/picker.mjs';
export { default as TimePickPanel } from './components/time-picker/src/time-picker-com/panel-time-pick.mjs';
export { timeSelectProps } from './components/time-select/src/time-select.mjs';
export { ElTimeSelect } from './components/time-select/index.mjs';
export { timelineItemProps } from './components/timeline/src/timeline-item.mjs';
export { ElTimeline, ElTimelineItem } from './components/timeline/index.mjs';
export { tooltipEmits, useTooltipModelToggle, useTooltipModelToggleEmits, useTooltipModelToggleProps, useTooltipProps } from './components/tooltip/src/tooltip.mjs';
export { useTooltipTriggerProps } from './components/tooltip/src/trigger.mjs';
export { useTooltipContentProps } from './components/tooltip/src/content.mjs';
export { TOOLTIP_INJECTION_KEY } from './components/tooltip/src/constants.mjs';
export { ElTooltip } from './components/tooltip/index.mjs';
export { LEFT_CHECK_CHANGE_EVENT, RIGHT_CHECK_CHANGE_EVENT, transferCheckedChangeFn, transferEmits, transferProps } from './components/transfer/src/transfer.mjs';
export { ElTransfer } from './components/transfer/index.mjs';
export { ElTree } from './components/tree/index.mjs';
export { ElTreeSelect } from './components/tree-select/index.mjs';
export { ElTreeV2 } from './components/tree-v2/index.mjs';
export { genFileId, uploadBaseProps, uploadListTypes, uploadProps } from './components/upload/src/upload.mjs';
export { uploadContentProps } from './components/upload/src/upload-content.mjs';
export { uploadListEmits, uploadListProps } from './components/upload/src/upload-list.mjs';
export { uploadDraggerEmits, uploadDraggerProps } from './components/upload/src/upload-dragger.mjs';
export { uploadContextKey } from './components/upload/src/constants.mjs';
export { ElUpload } from './components/upload/index.mjs';
export { default as FixedSizeList } from './components/virtual-list/src/components/fixed-size-list.mjs';
export { default as DynamicSizeList } from './components/virtual-list/src/components/dynamic-size-list.mjs';
export { default as FixedSizeGrid } from './components/virtual-list/src/components/fixed-size-grid.mjs';
export { default as DynamicSizeGrid } from './components/virtual-list/src/components/dynamic-size-grid.mjs';
export { virtualizedGridProps, virtualizedListProps, virtualizedProps, virtualizedScrollbarProps } from './components/virtual-list/src/props.mjs';
export { watermarkProps } from './components/watermark/src/watermark.mjs';
export { ElWatermark } from './components/watermark/index.mjs';
export { tourEmits, tourProps } from './components/tour/src/tour.mjs';
export { tourStepEmits, tourStepProps } from './components/tour/src/step.mjs';
export { tourContentEmits, tourContentProps, tourPlacements, tourStrategies } from './components/tour/src/content.mjs';
export { ElTour, ElTourStep } from './components/tour/index.mjs';
export { anchorEmits, anchorProps } from './components/anchor/src/anchor.mjs';
export { ElAnchor, ElAnchorLink } from './components/anchor/index.mjs';
export { segmentedEmits, segmentedProps } from './components/segmented/src/segmented.mjs';
export { ElSegmented } from './components/segmented/index.mjs';
export { mentionEmits, mentionProps } from './components/mention/src/mention.mjs';
export { ElMention } from './components/mention/index.mjs';
export { ElInfiniteScroll } from './components/infinite-scroll/index.mjs';
export { ElLoading } from './components/loading/index.mjs';
export { vLoading as ElLoadingDirective, vLoading } from './components/loading/src/directive.mjs';
export { Loading as ElLoadingService } from './components/loading/src/service.mjs';
export { messageDefaults, messageEmits, messageProps, messageTypes } from './components/message/src/message.mjs';
export { ElMessage } from './components/message/index.mjs';
export { ElMessageBox } from './components/message-box/index.mjs';
export { notificationEmits, notificationProps, notificationTypes } from './components/notification/src/notification.mjs';
export { ElNotification } from './components/notification/index.mjs';
export { popoverEmits, popoverProps } from './components/popover/src/popover.mjs';
export { ElPopover, ElPopoverDirective } from './components/popover/index.mjs';
export { EVENT_CODE } from './constants/aria.mjs';
export { WEEK_DAYS, datePickTypes } from './constants/date.mjs';
export { CHANGE_EVENT, INPUT_EVENT, UPDATE_MODEL_EVENT } from './constants/event.mjs';
export { INSTALLED_KEY } from './constants/key.mjs';
export { componentSizeMap, componentSizes } from './constants/size.mjs';
export { default as ClickOutside } from './directives/click-outside/index.mjs';
export { vRepeatClick } from './directives/repeat-click/index.mjs';
export { default as TrapFocus } from './directives/trap-focus/index.mjs';
export { default as Mousewheel } from './directives/mousewheel/index.mjs';
export { useAttrs } from './hooks/use-attrs/index.mjs';
export { useDeprecated } from './hooks/use-deprecated/index.mjs';
export { useDraggable } from './hooks/use-draggable/index.mjs';
export { useFocus } from './hooks/use-focus/index.mjs';
export { buildLocaleContext, buildTranslator, localeContextKey, translate, useLocale } from './hooks/use-locale/index.mjs';
export { useLockscreen } from './hooks/use-lockscreen/index.mjs';
export { useModal } from './hooks/use-modal/index.mjs';
export { createModelToggleComposable, useModelToggle, useModelToggleEmits, useModelToggleProps } from './hooks/use-model-toggle/index.mjs';
export { usePreventGlobal } from './hooks/use-prevent-global/index.mjs';
export { useProp } from './hooks/use-prop/index.mjs';
export { usePopper } from './hooks/use-popper/index.mjs';
export { useSameTarget } from './hooks/use-same-target/index.mjs';
export { useTeleport } from './hooks/use-teleport/index.mjs';
export { useThrottleRender } from './hooks/use-throttle-render/index.mjs';
export { useTimeout } from './hooks/use-timeout/index.mjs';
export { useTransitionFallthrough, useTransitionFallthroughEmits } from './hooks/use-transition-fallthrough/index.mjs';
export { ID_INJECTION_KEY, useId, useIdInjection } from './hooks/use-id/index.mjs';
export { useEscapeKeydown } from './hooks/use-escape-keydown/index.mjs';
export { usePopperContainer, usePopperContainerId } from './hooks/use-popper-container/index.mjs';
export { useDelayedRender } from './hooks/use-intermediate-render/index.mjs';
export { useDelayedToggle, useDelayedToggleProps } from './hooks/use-delayed-toggle/index.mjs';
export { FORWARD_REF_INJECTION_KEY, useForwardRef, useForwardRefDirective } from './hooks/use-forward-ref/index.mjs';
export { defaultNamespace, namespaceContextKey, useGetDerivedNamespace, useNamespace } from './hooks/use-namespace/index.mjs';
export { ZINDEX_INJECTION_KEY, defaultInitialZIndex, useZIndex, zIndexContextKey } from './hooks/use-z-index/index.mjs';
export { arrowMiddleware, getPositionDataWithUnit, useFloating, useFloatingProps } from './hooks/use-floating/index.mjs';
export { useCursor } from './hooks/use-cursor/index.mjs';
export { useOrderedChildren } from './hooks/use-ordered-children/index.mjs';
export { SIZE_INJECTION_KEY, useGlobalSize, useSizeProp, useSizeProps } from './hooks/use-size/index.mjs';
export { useFocusController } from './hooks/use-focus-controller/index.mjs';
export { useComposition } from './hooks/use-composition/index.mjs';
export { DEFAULT_EMPTY_VALUES, DEFAULT_VALUE_ON_CLEAR, SCOPE, emptyValuesContextKey, useEmptyValues, useEmptyValuesProps } from './hooks/use-empty-values/index.mjs';
export { ariaProps, useAriaProps } from './hooks/use-aria/index.mjs';

const install = installer.install;
const version = installer.version;

export { install, version };
//# sourceMappingURL=index.mjs.map

注意,这个index.mjs并不是打包后文件。因此这时引用混用是没有问题的。

复制代码
import {ElDatePicker} from 'element-plus';
import {ElDatePicker} from 'element-plus/components/date-picker';

组件打包后,前端如何使用?关键是要能把一个个文件代码再拆开,否则无法兼容分散引用。因此,不能用webpack的模式打包,因为无法拆开。只能自己建立一个打包格式,如:

复制代码
{
  modules:[
    {
      path:'/npm/element-plus/es/components/date-picker
      code:......
    },
    {
      path:'/npm/element-plus/es/components/card
      code:......
    },
  ]
}

浏览器前端拿到这个打包结果后,需要一一拆开。在es5模式下,因为require函数是你自己实现的,你的require函数可以支持内联加载(inline module import),即直接加载code而不是url,不赘述。

但是es6下,import和import()是浏览器内置的,你无法修改。特别的,nodejs的import有实现这种模式:Modules: ECMAScript modules | Node.js v24.0.2 Documentation

那如何办呢?

(1)等待新的es标准,各大浏览器支持内联加载(inline module import

(2)在service worker中,拦截fetch

本文主要讲service worker中如何处理打包拆包。

拆包后代码可以放在浏览器cache中,也可以放在内存中。放在cache中比较慢,好处是浏览器关闭后,下次打开可以直接用。

如:拆到内存中:

复制代码
/**
 * 把小模组存储到内存中
 */
const memoryCache={};
const cacheHeaders={};
let foil={
  onActivate(){
    return clients.claim();
  },
  getModule(req,cb){
    let u=new URL(req.url);
    let url=u.pathname+u.search;
    // console.log(prefix,url);
    let m=memoryCache[url];
    let res;
    if (m){
      res=new Response(m.code,{
        status:m.status,
        headers:{
          'Content-Type': 'application/javascript',
          'Location':m.location,
        }
      });
    } 
    cb(null,res);
  },
  async parseBundleResult(bundleObj,cacheHeaders){
    //console.log(prefix,''bundle result:',bundleObj);
    for(let i=0;i<bundleObj.modules.length;i++){
      let m=bundleObj.modules[i];
      //console.log(prefix,m.path,m.location);
      /**
       * 作业打包时,可能只返回了依赖列表而没有代码,没有代码就不加入cache中。
       */
      if (!m.code) continue;
      if (m.location){
        let originDir=m.path.slice(0, m.path.lastIndexOf("/") + 1);
        let realDir=m.location.slice(0, m.location.lastIndexOf("/") + 1);
        if (originDir!=realDir){
          /**
           * 重定向的路径变了,会影响相对路径的import/require加载,
           * 增加一个单独的路由。
           */
          memoryCache[m.location]={status:200,code:m.code};
          memoryCache[m.path]={status:301,location:m.location};
        }
        else memoryCache[m.path]={status:200,code:m.code};
      }
      else memoryCache[m.path]={status:200,code:m.code};
  
      cacheHeaders[m.path]=m.cacheHeaders['last-modified'];
      if (m.location) cacheHeaders[m.location]=m.cacheHeaders['last-modified'];
    }
  },
  getCacheHeaders(){
    return cacheHeaders;
  }
}

如:拆到cache中:

复制代码
/**
 * 把小模组存储到浏览器缓存中
 */
const CACHE_NAME = "foil-spa-vue-v1";
const key_cacheHeaders='/_cache_meta_info';

function module2Response(m){
  let res1,res2;
  let status=200;
  let code=m.code;
  let headers={
    'Content-Type': 'application/javascript',
    'Content-Length':new Blob([m.code]).size.toString(),
  }
  if (m.location){
    //console.log(prefix,m.path,m.location);
    let originDir=m.path.slice(0, m.path.lastIndexOf("/") + 1);
    let realDir=m.location.slice(0, m.location.lastIndexOf("/") + 1);
    if (originDir!=realDir){
      /**
       * 重定向的路径变了,会影响相对路径的import/require加载,
       * 增加一个单独的路由。
       */
      res2={
        path:m.location,
        res:new Response(m.code,{status:200,headers})
      };

      status=301;
      code=undefined;
      headers={
        'Content-Type': 'application/javascript',
        'Location':m.location,
        'Content-Length':0
      }
    }
  }
  res1={
    path:m.path,
    res:new Response(code,{status,headers})
  };
  return [res1,res2];
}

let foil={
  onActivate(){
    let t1=new Date().getTime();
    let task=new Promise(async function(resolve,reject){
      try{
        let cacheNames=await caches.keys();
        await cacheNames
          .filter(function (cacheName) {
            // 过滤掉不需要的缓存
            return (
              cacheName.startsWith("foil-spa-vue-") && cacheName !== CACHE_NAME
            );
          })
          .map(function (cacheName) {
            // 删除旧的缓存
            return caches.delete(cacheName);
          });
        // 立即接管所有客户端
        await clients.claim();
        console.log(prefix,"activate clear cache,",new Date().getTime()-t1,'ms');
        resolve();
      }
      catch(err){
        reject(err);
      }
    });
    return task;
  },
  getModule(req,cb){
    caches.match(req).then(function (res) {
      cb(null,res);
      return res;
    },cb);
  },
  async parseBundleResult(bundleObj,cacheHeaders){
    let cache=await caches.open(CACHE_NAME);
    let tasks=[];
    for(let i=0;i<bundleObj.modules.length;i++){
      let m=bundleObj.modules[i];
      if (!m.code) continue;
      let [res1,res2]=module2Response(m);
      tasks.push(cache.put(res1.path,res1.res));
      if (res2) tasks.push(cache.put(res2.path,res2.res));
      cacheHeaders[m.path]=m.cacheHeaders['last-modified'];
      if (m.location) cacheHeaders[m.location]=m.cacheHeaders['last-modified'];
    }
    let cacheHeadersJSON=JSON.stringify(cacheHeaders);
    tasks.push(cache.put(key_cacheHeaders,new Response(cacheHeadersJSON,{
      headers:{
        'Content-Length':new Blob([cacheHeadersJSON]).size.toString()
      }
    })));
    await Promise.all(tasks);
  },
  async getCacheHeaders(){
    let cacheHeaders={};
    let cache=await caches.open(CACHE_NAME);
    let cacheRes=await cache.match(key_cacheHeaders);
    if (cacheRes){
      cacheHeaders=await cacheRes.json();
    }
    return cacheHeaders;
  }
}

在service worker中拦截fetch调用,注意要处理post的body。

复制代码
self.addEventListener("fetch", function (event) {
  //console.log(prefix,'fetch',event.request.url);
  let task = new Promise(function (resolve, reject) {
    foil.getModule(event.request, function (err, res) {
      if (err) {
        reject(err);
      } else if (res) resolve(res);
      else {
        /**
         * 看到event.request.destination是'script',但是后台收到的req.headers['sec-fetch-dest']是'empty'
         * 这导致es6下,后台对/npm/react/umd/react.development.js这个es5文件不能转码。
         * 增加'sec-fetch-dest'无效,只能增加'tm-sec-fetch-dest'。
         * es6下为了处理import ".";这种非法语句,需要把原始referer传递到后台。
         * 注意:req.referrer单词与referer不同,多一个r字母。
         *
         * Request 对象的 headers 是只读的,没办法直接进行修改。当你使用 req.headers.set() 时,浏览器会报错,提示 Headers are immutable(头部不可变)。
         */
        let req = event.request;
        let bodyPromise;
        if (req.method === "POST" || req.method === "PUT") {
          const contentType = req.headers.get("Content-Type");
          if (contentType && contentType.includes("application/json")) {
            bodyPromise = req.json().then(function(json){return JSON.stringify(json);});
          } else if (contentType && contentType.includes("form")) {
            bodyPromise = req.formData();
          } else {
            bodyPromise = req.text();
          }
        } else {
          bodyPromise = Promise.resolve(null);
        }
        bodyPromise.then(function (body) {
          const newHeaders = {
            "tm-referer": req.referrer,
            "tm-sec-fetch-dest": req.destination,
          };
          for (let [key, value] of req.headers.entries()) {
            newHeaders[key] = value;
          }
          let mode = req.mode;
          if (mode === "navigate") {
            /**
             * 修正 mode 属性,避免使用 'navigate'
             * 对于导航请求,降级为 cors 或 same-origin
             * 否则报错:Uncaught (in promise) TypeError: Failed to construct 'Request': Cannot construct a Request with a RequestInit whose mode member is set as 'navigate'.
             */
            mode = req.url.startsWith(self.origin) ? "same-origin" : "cors";
          }
          req = new Request(req, {
            method: req.method,
            headers: newHeaders,
            body: body,
            mode: mode,
            credentials: req.credentials,
            cache: req.cache,
            redirect: req.redirect,
            referrer: req.referrer,
          });
          res = fetch(req).then(
            function (data) {
              return data;
            },
            function (err) {
              /**增加then后,不在浏览器显示重复的错误信息*/
            }
          );
          resolve(res);
          return body;
        }, reject);
      }
    });
  });
  event.respondWith(task);
});

注意:

(1)每次后台打包,可能要花2-3秒,最好把打包结果保存到文件,下次直接读取。或者服务器启动时,预热打包一次。

(2)前端如何更新包内个别文件。前端记录每个文件的last-modified,打包时全部传到后台,文件多时可能有几十K。

(3)service worker的使用限制,必须是https有合法ca证书,且不能是https://localhost

如此,SPA宿主页加载速度可以提高40-50%。

相关推荐
斯~内克1 小时前
深入解析前端 JSBridge:现代混合开发的通信基石与架构艺术
前端·架构
Jacky-0081 小时前
ajax post请求 解决自动再get请求一次
前端·javascript·ajax
不写八个1 小时前
Vue3.0教程005:watch监视ref定义的【基本类型】数据和【对象类型】数据
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=1 小时前
【Vue篇】组件的武林绝学:状态风暴下的乾坤挪移术
前端·javascript·vue.js
逃逸线LOF2 小时前
CSS之网页元素的显示与隐藏(旧土豆网遮罩案例)
前端·css
_xaboy2 小时前
开源表单设计器FcDesigner配置多语言教程
前端·vue.js·低代码·开源·表单设计器
文艺倾年2 小时前
【系统架构师】2025论文《WEB系统性能优化技术》
前端·性能优化·系统架构
铃木隼.2 小时前
Web技术与Nginx网站环境部署
前端·nginx·php
郭尘帅6662 小时前
Vue3 父子组件传值, 跨组件传值,传函数
前端·javascript·vue.js
charlee443 小时前
使用Vite创建一个动态网页的前端项目
前端·javascript·vite