自动 Import 工具,前端打字员的自我救赎

god14 7年前
   <h2>自动 import 工具</h2>    <p>作为一个前端打字员,一个经常遇到的场景就是在 <strong>路由文件中引入模块</strong> ,比如这样</p>    <p><img src="https://simg.open-open.com/show/65828a9812ac735c29c76b56c85f6bb4.jpg"></p>    <p>在 router/index.js 中写入</p>    <pre>  <code class="language-javascript">import Vue from 'vue'  import Router from 'vue-router'    const About = () => import('../pages/About.vue')  const Home = () => import('../pages/Home.vue')    Vue.use(Router)  ...</code></pre>    <p>如果修改了模块的名字,增加了模块或者删除了模块,就需要重新修改这个路由文件</p>    <p>总是做这么机械的事情无异于消耗我这个 <strong>前端打字员</strong> 的寿命</p>    <p>不能忍,遂写个工具</p>    <h3>整理思路如下</h3>    <p><img src="https://simg.open-open.com/show/a440fd71faee25d55badd6b28fa2d32a.jpg"></p>    <p>其中,监视目录下文件的变动依靠的是 <strong>node</strong> API 中 fs.watch(filename[, options][, listener])</p>    <p>替换目标文件中引入模块的部分,则是通过正则来实现</p>    <p>在这里五星推荐一个验证正则是否正确的网站, <a href="/misc/goto?guid=4959756362441703254" rel="nofollow,noindex">regexr</a></p>    <h3>代码实现</h3>    <p>监视包含模块的目录</p>    <pre>  <code class="language-javascript">fs.watch(dir, {    recursive: true // 目录下子目录也被监视  }, (event, filename) => {   // event 是文件变动的类型,添加文件、删除文件和修改文件名都是'rename' 事件  // filename 是变化的文件或目录    if(event === 'rename'){ // 判断文件变动类型          }  })</code></pre>    <p>当发生 <strong>rename</strong> 事件后,需要重新获得目录下( <strong>from</strong> )所有的模块,包括模块名 <strong>moduleName</strong> ,模块文件相对于引用模块文件( <strong>to</strong> )的相对路径 <strong>modulePath</strong> ,将它们存入变量 <strong>modules</strong> 中</p>    <p>实际项目中,模块通常都是 <strong>.vue</strong> 文件,或者 <strong>.jsx</strong> 文件,因此只将这些作为模块,在路由文件中引用</p>    <p>另外有些模块文件因为各种原因,希望人工引入,而不被 <strong>watch</strong> ,这样的文件存入 <strong>excludeArr</strong> 中</p>    <pre>  <code class="language-javascript">const _ = require('lodash')  let excludeArr = [...]  let modules = []  let extname = '.vue'  let from = './src/pages'  let to = './src/router/index.js"'    const mapDir = d => {      // 获得当前文件夹下的所有的文件夹和文件      const [dirs, files] = _(fs.readdirSync(d)).partition(p =>          fs.statSync(path.join(d, p)).isDirectory()      )        // 映射文件夹      dirs.forEach(dir => {          modules.concat(mapDir(path.join(d, dir)))      })        // 映射文件      files.forEach(file => {          // 文件后缀名          let filename = path.join(d, file)          if (path.extname(file) === extname) {              if (!excludeArr.includes(path.resolve(__dirname, filename))) {                  let moduleName = path.basename(file, extname)                  // 若存在 -                  if (moduleName.match('-')) {                      moduleName = moduleName.replace(                          /(-)(.{1})/,                          (match, p1, p2, offset, string) => p2.toUpperCase()                      )                  }                  modules.push({                      moduleName,                      modulePath: path.relative(path.dirname(to), filename)                  })              }          }      })  }</code></pre>    <p>生成好新的待引入的模块后,接下来就是在路由文件中,将对应的内容替换掉</p>    <p>所以需要读写文件以及正则替换</p>    <pre>  <code class="language-javascript">const regex = /\/\*\sautoImport(.*\n)*\/\*\sautoImport\s\*\//g                  let importStr = ''                  modules.forEach((m, index) => {                      importStr =                          importStr +                          fillTemplate(template, m.moduleName, m.modulePath) +                          (cache.length - 1 === index ? '' : '\n')                  })                  fs.readFile(to, 'utf8', (err, data) => {                      if (err) return console.log(err)                      let result = ''                      if (data.match(regex)) {                          result = data.replace(                              regex,                              `/* autoImport */  ${importStr}  /* autoImport */`                          )                      } else {                          /* 首次插入在文件最后的import插入 */                          result = data.replace(                              /(.*import.*)(\n)([^(import)])/,                              (match, p1, p2, p3, offset, string) => {                                  return `${p1}  /* autoImport */  ${importStr}  /* autoImport */  ${p3}`                              }                          )                      }                        fs.writeFile(to, result, 'utf8', err => {                          if (err) return console.log(err)                      })                  })</code></pre>    <p>其中 /\/\*\sautoImport(.*\n)*\/\*\sautoImport\s\*\//g 是用于匹配两段注释 /* autoImport */ 及其中间的内容</p>    <pre>  <code class="language-javascript">import Vue from 'vue'  import Router from 'vue-router'  /* autoImport */  const About = () => import('../pages/About.vue')  const Home = () => import('../pages/Home.vue')  /* autoImport */    Vue.use(Router)</code></pre>    <p>当第一次使用,没有 /* autoImport */ 时,就需要在最后一个 <strong>import</strong> 后面,插入引入的模块</p>    <pre>  <code class="language-javascript">data.replace(                              /(.*import.*)(\n)([^(import)])/,                              (match, p1, p2, p3, offset, string) => {                                  return `${p1}  /* autoImport */  ${importStr}  /* autoImport */  ${p3}`</code></pre>    <p>在这里还可以自定义了引入模块的方式,例如懒加载, "const moduleName = () => import(modulePath)"</p>    <pre>  <code class="language-javascript">const template = "const moduleName = () => import(modulePath)"  const fillTemplate = (template, moduleName, modulePath) =>      template          .replace('moduleName', moduleName)          .replace('modulePath', `'${modulePath}'`)</code></pre>    <p>为了工具的灵活性,把可配置项写成json文件的形式</p>    <pre>  <code class="language-javascript">{      "extname": ".vue",      "from": "src/pages",      "to": "src/router/index.js",      "template": "const moduleName = () => import(modulePath)",      "exclude": [          "./src/pages/login.vue",          "./src/pages/404.vue",          "./src/pages/overall/**",          "./src/pages/account-result/**"      ]  }</code></pre>    <p>然后通过以下的方式来获得</p>    <pre>  <code class="language-javascript">const config = fs.readFileSync('./autoImport.json')  const { extname, from, to, template, exclude } = JSON.parse(config)</code></pre>    <h3>后记</h3>    <p>下一步准备把这个工具写成webpack的插件,名字我都起好了, <a href="/misc/goto?guid=4959756362536127369" rel="nofollow,noindex">AutoImportPlugin</a> ,先在github上占了个坑, <strong>顺手给颗星,不用改Bug</strong></p>    <p>同时准备用更加成熟的模块 <a href="/misc/goto?guid=4958861581608562171" rel="nofollow,noindex">chokidar</a> 来代替原生的 <strong>watch</strong></p>    <p>工具有问题提issue啊</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000012792016</p>    <p> </p>