Recently, a small game Y of the company needed to implement a localized theme, that is, different regions need to show different themes, and the game has a large number of images, how to gracefully and quickly load the correct skin images became particularly important.

CSS style switches are available at juejin.cn/post/684490… This article is about how to switch images based on the theme of getting different resources.

The initial plan

Image peels. Based on past experience, we generally import images using require, thanks to webPack’s dependency management. For example, with this approach (require(‘ @assets/img/${theme}/bg.png ‘)), webpack will add all the files from @assets/img to the bundle so that the corresponding images can be found at runtime. The concrete implementation is as follows:

<template> <! <div class="y-content" :style="{backgroundImage: contentBg? `url(${contentBg})` : 'auto'}"> <img class="y-content__reward" :src="rewardImg" /> </div> </template> <script> data: () => ({ theme: 'blcak' }), computed: {contentBg() {try {// this.theme is a folder, put different skins in different folders, Return require(' @assets/img/${this.theme}/ contentbg.png ')} catch (err) {return "; } }, rewardImg() { try { return require(`@assets/img/${this.theme}/rewardImg.png`) } catch (err) { return ''; } } } </script>Copy the code

The above code can basically solve most of the skin changing requirements, but for the project where the images need to be pre-loaded, we also need to distinguish the images of different themes to facilitate the optimization of pre-loading. Since the compiled image links are different from those before compilation, we obtain the compiled image links. In general projects, such as those built by using the official scaffolding vue-CLI, all images will be processed by urL-loader and put into the same folder image. In this way, images with the same name in different folders before compilation will have different hash after compilation, so we cannot distinguish images with different themes.

Therefore, first of all, we need to place different theme images into different folders, which also applies to urL-loader

// vue-cli configures const imagesRule = config.module.rule('images'); ImagesRule. USES. The clear () / / clear the original images imagesRule loader configuration. The test (/ white /. +. (JPG | | PNG GIF | SVG) $/). The use (' url - loader) .loader('url-loader') .options({ name:"img/white/[name].[hash:8].[ext]", limit: 8192})/add a theme configuration/config. The module. The rule (" image2 "). The test (/ black /. +. (JPG | | PNG GIF | SVG) $/). The use (' url - loader) .loader('url-loader').options({name:"img/black/[name].[hash:8].[ext]", limit: 8192}) /white/.+.(png|svg|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, name: 'img/white/[name].[hash:8].[ext]', } } ], }, { test: /black/.+.(png|svg|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, name: 'img/black/[name].[hash:8].[ext]', } } ], }, ]Copy the code

Is this the end of compiling images for different themes into different folders? Not yet, we still need to get the link of the compiled image to load the theme image in advance when entering the game page. Here we can write a Webpack Plugin to help us collect the corresponding image and produce the JSON file of each theme. The plug-in code is as follows:

X class WebPackSouceManifest {// Define 'apply' as its prototype method, This method to the compiler as a parameter to the constructor (option = {}) {/ / black theme file name enclosing blackManifestName = option. BlackManifestName | | 'js/blackManifest. Json / / white theme file name enclosing whiteManifestName = option. WhiteManifestName | |' js/whiteManifest json ' this.blackJsonData = { code: 0, data: [] } this.whiteJsonData = { code: 0, data: []} this. AddFileToSouceManifest. Bind (this)} the apply (compiler) {/ / to be attached to the specified event hooks compiler. The hooks, emit. TapAsync ( 'HashServiceWorkerStartPlugin', (compilation, callback) => { const allBuildFiles = Object.keys(compilation.assets) allBuildFiles.forEach((file) => { if (file.indexOf('white') ! == -1) { this.addFileToSouceManifest('white', file) } else { this.addFileToSouceManifest('black', file) } }) const sourceBlack = JSON.stringify(this.blackJsonData) const sourceWhite = JSON.stringify(this.whiteJsonData)  compilation.assets[this.blackManifestName] = { source: () => { return sourceBlack; }, size: () => { return sourceBlack.length; } } compilation.assets[this.whiteManifestName] = { source: () => { return sourceWhite; }, size: () => { return sourceWhite.length; } } callback() } ); } addFileToSouceManifest(type, file) { let fileItem = { src: file, } if (/.js$/.test(file)) { fileItem.type = 'text' } else if (/.js.map$/.test(file)) { fileItem.type = 'json' } if (type === 'white') { this.whiteJsonData.data.push(fileItem) } else { this.blackJsonData.data.push(fileItem) } } } module.exports = WebPackSouceManifest;Copy the code

So we get the json of the image list of different themes, and then load the images in the other table after getting the list through Ajax when entering the page. Although the above method can achieve the requirements, it is too complicated. Is there an easy way? Of course there is.

Optimization scheme

After careful analysis of the above code, we finally want to obtain the compiled image result, so if we can generate an image object, the name of the image as key, the compiled image result as value, then the above code can be simplified as follows:

<template> <! <div class="y-content" :style="{backgroundImage: contentBg? `url(${contentBg})` : 'auto'}"> <img class="y-content__reward" :src="rewardImg" /> </div> </template> <script> data: () => ({ theme: 'middleEast' }), computed: {contentBg() {return this.$root.skinImage['contentBg'] // contentBg = name} rewardImg() { return this.$root.skinImage['rewardImg'] } } </script>Copy the code

So the code is clean and there is no need for the entire require and try catch. So how do we implement the skinImage object

The answer is to use webpack’s require.context

Get a specific context by executing require.context, which is used to automate the import of modules. In front end engineering, if many modules are imported from a folder, use this API, which iterates through the specified files in the folder and automatically imports, eliminating the need to explicitly call the IMPO each time Rt import module, the specific usage is not described here, please refer to the official document requireContext

So let’s write a function that automatically imports images and generates a skinImage object

const black = require.context('.. /black', true, /.(png|jpg|gif)$/); const middleImageObj = {}; Black. Keys ().foreach (path => {const key = path.slice(2, -4); blackImageObj[key] = rawSkin(path); });Copy the code

Since require.context is executed during compilation and not at runtime, all parameters must be static. Here we also need to pre-fetch the image with the white theme, as shown below

const black = require.context('.. /black', true, /.(png|jpg|gif)$/); const middleImageObj = {}; Black. Keys ().foreach (path => {const key = path.slice(2, -4); blackImageObj[key] = rawSkin(path); });Copy the code

So we have two theme image objects, then we just need to assign one object to the root component’s skinImage, and we are done.