当前位置: 首页 > news >正文

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-pickercode:......},{path:'/npm/element-plus/es/components/cardcode:......},]
}

浏览器前端拿到这个打包结果后,需要一一拆开。在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%。

相关文章:

  • Index-AniSora技术升级开源:动漫视频生成强化学习
  • 深入解析FramePack:高效视频帧打包技术原理与实践
  • 什么叫生成式人工智能?职业技能的范式转移与能力重构
  • C++:迭代器
  • c/c++的opencv高斯模糊
  • 超长文本注意力机制如何突破传统 O(n²) 瓶颈
  • 大模型的说谎行为
  • 大模型——多模态检索的RAG系统架构设计
  • OD 算法题 B卷 【最佳植树距离】
  • Nginx配置记录访问信息
  • QT高DPI支持
  • AI人工智能的SGLang、vllm和YaRN大语言模型服务框架引擎的对比
  • HarmonyOS应用开发入门宝典——项目驱动学习法实践
  • 中国机加工的市场概况及冷镦技术对于机加工替代的趋势
  • Java 04 API
  • 深入浅出人工智能:机器学习、深度学习、强化学习原理详解与对比!
  • docker容器知识
  • JIT即时编译器全面剖析:原理、实现与优化
  • Java实现基于bitmap的字符串去重统计
  • Pycharm-jupyternotebook不渲染
  • 海南省市监局与香港标准及检定中心签署合作协议,加快检验检测国际化
  • 济南一医院救护车未执行紧急任务时违规鸣笛
  • 上海浦江游览南拓新航线首航,途经前滩、世博文化公园等景点
  • 人民日报评论员观察:稳就业,抓好存量、增量、质量
  • 上海青少年书法学习园开园:少年以巨笔书写《祖国万岁》
  • 竞彩湃|英超欧冠悬念持续,纽卡斯尔诺丁汉能否拿分?