如何将多个静态文件捆绑为 zip 存档以供下载

How to bundle multiple static files as zip archive for download

我的 Jekyll 页面旨在为编码书籍的练习提供示例解决方案。 解决方案是一堆 .cpp 文件(C++ 代码文件)存储在我的 Jekyll 项目中的一个文件夹中,这样我就可以在 IDE.

中打开同一个文件夹

我已经设法为每本书的章节自动生成一页,将相关解决方案显示为代码块列表(每个练习一个)。我通过遍历 site.static_files 并通过文件名中的数字识别文件来做到这一点(例如,第 1 章的两个解决方案:01_1_FirstSolution.cpp01_2_SecondSolution.cpp

现在,我还想为每本书的章节提供一个 zip 存档,其中包含相关的 .cpp 文件。我不想手动制作 zip 文件,因为那样我将无法再简单地更改其中一个代码文件。理想情况下,我想在循环 site.static_files 并过滤相关文件的同时构建一个 zip 文件。

我搜索这个的时候,主要是找到了一些速度优化插件,用于打包和压缩资源。我是 运行 Jekyll Windows。

Jekyll 主要是静态网站构建器,因此在使用 Jekyll 构建之前 运行 一个程序或 shell 脚本并将它们输出到 site.static_files 可能是理想的选择您希望输出到的位置如上。

您可以在 运行ning Jekyll 之前使用脚本来压缩您的文件,然后 运行 Jekyll 构建过程。

还有 Generator plugins 您可以使用插件或 Ruby 对 运行 脚本的 system 命令来实现类似的结果你的构建。

如果可以获取文件内容(fetch etc.) you may use JSZip 库将它们打包成 Zip。我不确定这是否适合你在 jekyll 中...但我会尝试这样做。

要尝试示例,只需创建一个 html 文件或 open my example with fetch

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js" integrity="sha512-uVSVjE7zYsGz4ag0HEzfugJ78oHCI1KhdkivjQro8ABL/PRiEO4ROwvrolYAcZnky0Fl/baWKYilQfWvESliRA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js" integrity="sha512-csNcFYJniKjJxRWRV1R7fvnXrycHP6qDR21mgz1ZP55xY5d+aHLfo9/FcGDQLfn2IfngbAHd8LdfsagcCqgTcQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> 
</head>
<body>
<script>
    var zip = new JSZip();
    zip.file("Hello.txt", "Hello World\n");
    zip.file("Hello2.txt", "Hello2 World\n");
    //var img = zip.folder("images");
    //img.file("smile.gif", imgData, {base64: true});
    zip.generateAsync({type:"blob"})
    .then((content) => {
        // see FileSaver.js
        saveAs(content, "example.zip");
    });
</script>
</body>
</html>

jekyll-zip-bundler 插件。

如何使用

作为多个参数的文件名:

{% zip archiveToCreate.zip file1.txt file2.txt %}

文件名中的空格:

{% zip archiveToCreate.zip file1.txt folder/file2.txt 'file with spaces.txt' %}

包含文件列表的变量也是可能的:

{% zip ziparchiveToCreate.zip {{ chapter_code_files }} %}

插件代码

# frozen_string_literal: true

# Copyright 2021 by Philipp Hasper
# MIT License
# https://github.com/PhilLab/jekyll-zip-bundler

require 'jekyll'
require 'zip'
# ~ gem 'rubyzip', '~>2.3.0'

module Jekyll
  # Valid syntax:
  # {% zip archiveToCreate.zip file1.txt file2.txt %}
  # {% zip archiveToCreate.zip file1.txt folder/file2.txt 'file with spaces.txt' %}
  # {% zip {{ variableName }} file1.txt 'folder/file with spaces.txt' {{ otherVariableName }} %}
  # {% zip {{ variableName }} {{ VariableContainingAList }} %}
  class ZipBundlerTag < Liquid::Tag
    VARIABLE_SYNTAX = /[^{]*(\{\{\s*[\w\-.]+\s*(\|.*)?\}\}[^\s{}]*)/mx.freeze
    CACHE_FOLDER = '.jekyll-cache/zip_bundler/'

    def initialize(tag_name, markup, tokens)
      super
      # Split by spaces but only if the text following contains an even number of '
      # Based on 
      # Extended to also not split between the curly brackets of Liquid
      # In addition, make sure the strings are stripped and not empty
      @files = markup.strip.split(/\s(?=(?:[^'}]|'[^']*'|{{[^}]*}})*$)/)
                     .map(&:strip)
                     .reject(&:empty?)
    end

    def render(context)
      # First file is the target zip archive path
      target, files = resolve_parameters(context)
      abort 'zip tag must be called with at least two files' if files.empty?

      zipfile_path = CACHE_FOLDER + target
      FileUtils.makedirs(File.dirname(zipfile_path))

      # Create the archive. Delete file, if it already exists
      File.delete(zipfile_path) if File.exist?(zipfile_path)
      Zip::File.open(zipfile_path, Zip::File::CREATE) do |zipfile|
        files.each do |file|
          # Two arguments:
          # - The name of the file as it will appear in the archive
          # - The original file, including the path to find it
          zipfile.add(File.basename(file), file)
        end
      end
      puts "Created archive #{zipfile_path}"

      # Add the archive to the site's static files
      site = context.registers[:site]
      site.static_files << Jekyll::StaticFile.new(site, "#{site.source}/#{CACHE_FOLDER}",
                                                  File.dirname(target),
                                                  File.basename(zipfile_path))
      # No rendered output
      ''
    end

    def resolve_parameters(context)
      # Resolve the given parameters to a file list
      target, files = @files.map do |file|
        next file unless file.match(VARIABLE_SYNTAX)

        # This is a variable. Look it up.
        context[file]
      end

      [target, files]
    end
  end
end

Liquid::Template.register_tag('zip', Jekyll::ZipBundlerTag)