擴展 lua require 的行為
來源 https://blog.codingnow.com/2015/10/lua_require_env.html
今天同事提了個需求,他希望可以給部分 lua 代碼(由策划編寫)做一個沙盒關起來。在 lua 里做沙盒很容易,只需要控制函數的環境就可以了。不過另一個附加需求是,這些代碼還可以直接利用 require 加載。
而我們又不想去修改系統的 api 接口,那么怎么做到這點呢?
首先, 我希望使用的時候看起來像這樣:
local xxx = require "xxx" (myEnv)
和傳統的 require 用法不同,可以在后面追加一個參數 myEnv 。這樣的話,每次 xxx 模塊被 require 時,它其實被重復運行一次,但會綁定不同的 _ENV
。
其次,既然模塊會被反復初始化,那么我們甚至還可以約定,每個這種沙盒封裝的模塊還可以接收 require 的傳入的額外參數。
做到這點很容易,我們只需要在 package.searchers 里追加一個自定義的 loader 然后並不返回加載的模塊 chunk ,而是做一個函數封裝。將 chunk 的運行推遲到傳入 myEnv 調用之后。
這樣,load chunk 本身還是依靠 require 的 package 機制緩存代碼的,只是每次調用后,重新綁定 _ENV
生成了一組新實例。
我在 gist 上貼了一組代碼實現這個特性:延遲綁定環境的 require 。
local M = {} function M.test(...) print(...) end return M
local package = package local debug = debug local function load_env(filename) local f,err = loadfile(filename) if f == nil then return err end return function() return function(env) if env then debug.setupvalue(f, 1, env) end return f(filename) end end end local function searcher_env(name) local filename, err = package.searchpath(name, package.upath) if filename == nil then return err else return load_env(filename) end end table.insert(package.searchers, searcher_env)
require "requirenv" package.upath = "./?.user.lua" local myprint = print local env = { print = function (...) myprint("hook", ...) end } local s = require "mymod"(env) s.test "hello world" -- hook hello world