Written in the book of the former



This time share a simple path replacement tool. The function is very simple, the key is to master:

  • Recursively traverse the folder directory
  • The re replaces the target content
  • Decompress the uploaded file to return the updated compressed file

Source code address: github.com/Aaaaaaaty/B…

Results the preview

The online preview

Comparison of results:

PS: The back end supports the corresponding paths of urls matching JS, CSS, IMG and background-image and replaces them respectively. Currently, the display is convenient, and the front end only passes one path to replace all matched source paths with target paths.

The whole process

  1. The front-end uploads the compressed package and the path field to be replaced
  2. Back-end decompression
  3. To recurse to the file directory, find.js/.css/.html files and match the replacement path
  4. Compress the entire file and return to the front end

The overall difficulty you might encounter is the use of the re and getting the compressed folder back locally once the replacement is done. Regex, which you haven’t written much about before, is a good opportunity to learn a lot about folders (note not file transfers!). The transmission stepped on a pit. After all, most of the time as a static server we only need to return a single file and we don’t need to return it to the front end as a folder.

Unzip the zip

In nodeJS documentation, the native API seems to support only gzip decompression, so we introduced the third-party plug-in Unzip to solve the problem.

let inp = fs.createReadStream(path) let extract = unzip.Extract({ path: TargetPath}) inp.pipe(extract) extract. On ('error', () => {cons(' error :' + err); }) extract. On ('close', () => {cons(' unzip '); })Copy the code

Where this plugin is a bit tricky is that it doesn’t say how to listen for ‘close’, ‘error’, etc. Or I went to see the source code found through the above form to call to success 🙂

Recursive file directory

The stat method of FS module is used to determine whether the current path is a file or folder to decide whether to continue traversal.

Function fsPathSys(path) {let stat = fs.statsync (path) if(stat.isdirectory ()) {fs.readdir(path, Function isDirectory(err, files) {if(err) {return err} else {files.foreach ((item, index) => { let nowPath = `${path}/${item}` let stat = fs.statSync(nowPath) if(! stat.isDirectory()) { ... somthing going on } else { fsPathSys(nowPath) } }) } } } else { ... }}Copy the code

Regular match

The focus of regex is on how to match to the desired place, and the order of substitution also needs to be considered. There are four places to match this time:

  • SRC under the script tag
  • Href under the link tag
  • SRC under the img tag
  • Url under background-image in the CSS

Since the SRC and href keywords before the target address may be in different tags, the original idea is that different types of files may be stored at different locations. Therefore, the matching principle is to extract script, link, img and background first, and then match SRC, href and URL keywords respectively.

Let data = [{'type': 'script', 'point': targetUrl}, {'type': 'link', 'point': targetUrl}, {'type': 'link', 'point': targetUrl}, {'type': 'img', 'point': targetUrl }, { 'type': 'background', 'point': targetUrl } ] data.forEach((obj, i) => { if(obj.type === 'script' || obj.type === 'link' || obj.type === 'img') { let bodyMatch = body.match(new RegExp(`<${obj.type}.*? >`, 'g')) if(bodyMatch) { bodyMatch.forEach((item, index) => { let itemMatch = item.match(/(src|href)\s*=\s*["|'].*? ["|']/g) if(itemMatch) { itemMatch.forEach((data, i) => { let matchItem = data.match(/(["|']).*\//g)[0].replace(/\s/g, '').slice(1) if(! replaceBody[matchItem]) { replaceBody[matchItem] = obj.point } }) } }) } } else if(obj.type === 'background') { let bodyMatch = body.match(/url\(.*? \)/g) if(bodyMatch) { bodyMatch.forEach((item, index) => { let itemMatch = item.match(/\(.*\//g)[0].replace(/\s/g, '').slice(1) if(! replaceBody[itemMatch]) { replaceBody[itemMatch] = obj.point } }) } } })Copy the code

See this article for the use of regexFull tutorial on regular expressionsIt’s really very detailed, so I’m not going to teach you how to suck eggs. In general, the above code gets an object, replaceBody. The key of this object is the path to be replaced, and the value is the path to be replaced:



An observant child might realize that if you just walked through the object now and made the substitution, you’d be done. Definitely not 🙂 because substitutions need to be in order, otherwise there will be big trouble.

For example we are going to replace ‘.. / CSS /’ and ‘./ CSS /’, if we replace the latter first then the previous ‘.. The ‘./ CSS /’ in/CSS/will also be replaced and the whole replacement will fail which is not what we want.

The current practice is to sort the keys in the object, with the longest in the first place, and then replace them. At least that won’t happen.

Object.keys(replaceBody).sort((a,b) => b.length-a.length) // Sort the objectsCopy the code

Another small point to note is when replacing ‘.’, since ‘.’ represents wildcards in the re. In this case, you need to replace all ‘.’ with ‘.’ before performing the following operations.

Compress the entire file and return to the front end

Considering that you are now passing back to the front end a folder, compress it. Write shell commands to compress folders by starting child processes. Node zlib module I can’t find how to compress folders. Those who know are welcome to share.)

let dirName = `${filePath}.tar.gz`
exec(`tar -zcvf ${dirName} ${filePath}`, (error, stdout, stderr) => {
	if (error) {
		cons(`exec error: ${error}`);
		return;
	}
	let out = fs.createReadStream(dirName)
	res.writeHead(200, {
		'Content-type':'application/octet-stream',
		'Content-Disposition': 'attachment; filename=' + dirName.match(/ip_.*/)[0] 
	})
	out.pipe(res) 
})
Copy the code

The e point here is to read the compressed package as a stream. Without the ‘Content-disposition’ field in the return header, the file returned would be like a buffer stream, without the folder hierarchy and so on. I looked it up and found it was the head.

Content-disposition is an extension of the MIME protocol that instructs the MIME user agent how to display attached files.

summary

The implementation of this small tool gives the author a new understanding of the details of regex and file decompression and HTTP transfer at the back end. Source code welcome clone~ on Git

reference

  • Noejs document
  • unzip
  • Node.js static file server

The last

The blog of Po author is updated from time to time – if you have any questions, please feel free to share them under issues.