Web前端小站 - 前端博客 https://www.nanbk.com/ 南岸有归的个人博客首页,南岸有归的技术作品,南岸有归的生活成长. 解锁gitlab和jenkins自动化部署项目 https://www.nanbk.com/archives/211/ 2023-11-03T11:44:00+08:00 最近用vue3和go语言重构了一下之前react和typescript、koa2开发的旧博客,每次发布都得手动进行部署,思来想去,还是决定把自动化部署给做一下,顺便解锁下新技能,分享一篇文章当笔记来看. 准备工作 我的gitlab和Jenkins都是使用docker进行部署的,gitlab服务性能占用比较大,读者请自行斟酌自己的服务器够不够用,官方是建议至少2c2g的服务器进行部署gitlab,如果服务器性能不够,可以自行安装例如gogs这类git服务,或者使用github和gitlab官方仓库,国内可以使用码云这类服务,也是同样可行. 首先进行Jenkins的安装,拉取Jenkins镜像 docker pull jenkinsci/blueocean 创建 Jenkins 目录,挂载容器 mkdir -p /usr/local/jenkins chmod 777 /usr/local/jenkins 启动容器 docker run -d -p 8090:8080 -p 50099:50000 -v /usr/local/jenkins:/var/jenkins_home --name myjenkins jenkinsci/blueocean 没有意外的话,可以通过ip加端口8090访问Jenkinsweb页面了,如果你需要绑定域名的话,我顺便贴一下nginx配置. nginx配置Jenkins域名访问 # Jenkins反代 server { listen 80; server_name xx.xxx.xxx; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:8099; } } 登录初始化 Jenkins 服务器如果性能不是很强的话,稍等会,耐心点等到出现这个界面时 通过cat /usr/local/jenkins/secrets/initialAdminPassword查看服务器Jenkins密码,或者你也可以进入docker容器中使用cat /var/jenkins_home/secrets/initialAdminPassword查看Jenkins密码. 剩下的新手入门,比如安装插件可以先通过安装推荐的插件,并且创建root用户等等不再赘述. 插件安装 接下来登录Jenkins找到系统管理安装插件,我的项目是使用GO和Vue还有自建的Gitlab另外我的Jenkins与部署项目的服务器不是同一台,所以我还装了Publish over SSH,所以我只需要按照这四个插件即可,如果环境不同的话安装对应的插件,安装完成记得重启下Jenkins. 环境配置 然后对我们的项目进行版本配置 先安装Node和Go,记得点应用,不然只是保存不会做安装的操作. 连接gitlab、github、码云等git参数 在Jenkins首页,新建一个任务,选择自由风格,创建完成进入项目配置General,选择git,填写你需要自动化部署的git仓库. 添加一个Credentials,使用Username with password,填写用户名和密码还有描述,添加完成后Credentials选择刚添加的这个,往下拉一点,找到Branches to build,这里填写你需要构建监控的分支,我这边只需要监听master,所以我的配置是 */master 如果你需要监听的分支不同,修改为自己的分支名就行. 构建触发器 找到Build when a change is pushed to GitLab. GitLab webhook URL: http://xx.xx.xx/project/test,勾选,复制http://xx.xx.xx/project/test等下在gitlab中需要用到. 进入你的代码仓库,进入项目设置找到Webhook后点击Webhook添加url为上面的url连接,找到配置中的触发事件,触发Jenkins自动化部署的配置,这里是,我这边只需要推送事件,如果你没有https,记得把启用SSL验证给关闭,然后添加Webhook,添加完成后可以测试一下能不能连接到Jenkins,如果返回了200即是成功.如果Test出现如下的403错误,则需要在Jenkins里设置一下安全策略,Jenkins全局安全设置中跨站请求伪造保护中取消启用代理兼容. 构建 此时我们的Jenkins和代码仓库已经连接成功了,指定的分支推送Jenkins会拉取最新代码触发自动化部署操作,接下来我们需要对,构建环境配置、打包进行处理. 构建环境配置 Jenkins中找到创建的项目进行配置,我们一开始环境配置安装Node的版本,就是在这个时候使用,拿Vue项目来说,就需要Node,所以我们勾选Provide Node & npm bin/ folder to PATH,这个选项,选择你安装的Node版本. Jenkins中找到创建的项目进行配置,构建步骤这个配置勾选执行shell,拿vue项目做个例子,配置是如下: node -v npm -v npm install npm run build 打印一些版本信息,执行npm install和npm run build打包命令,到了这一步自动化部署已经完成了,在/usr/local/jenkins/workspace目录也就是我们安装Jenkins目录下指定的项目目录下已经有我们打包好的文件了. 构建后的操作 在构建完成之后,我们可能需要一些额外的操作,例如移动一些目录,删除一些文件,更改一些配置等等,我们都可以在这个构建后的操作配置中进行配置,例如我的Jenkins服务器和发布服务器不是同一个,这时就需要使用到远程ssh上传,上传后进行发布服务器进行一些操作了,如果读者不需要,可以忽略这一步. 构建后ssh连接发布服务器可以使用Publish over SSH这个插件,在Jenkins系统配置底部找到Publish over SSH插件,新增一个SSH Server,服务器ip和ssh账户密码填入,Remote Directory是ssh上传的目录,添加完成后再进入到项目配置,找到构建后操作选择Send build artifacts over SSH,Name选择Publish over SSH新增的配置. Source files为当前项目目录,也就是git上你项目的目录,例如vue项目,打包后的文件就在当前源文件的dist文件夹下,所以这里填写dist/**表示dist下的所有文件 Remote directory为配置的ssh服务器远程目录,注意这里是Publish over SSH中Remote Directory为根目录,也就是说Publish over SSH填写了/www这里添加/build,上传的目录最终会到/www/build中. Exec command为ssh上传后的操作,例如我的vue项目需要做一些操作,这里贴一下我得命令: echo "进入到主目录" cd /www/wwwroot/vue3Blog/view echo "删除原来的文件夹" rm -rf /www/wwwroot/vue3Blog/view/dist echo "创建dist文件夹" mkdir dist echo "查看当前文件列表" ls echo "移动上传的build文件夹中的dist" mv /www/wwwroot/vue3Blog/view/build/dist/* /www/wwwroot/vue3Blog/view/dist/ echo "删除上传的文件夹build" rm -rf /www/wwwroot/vue3Blog/view/build echo "进入到主目录下的dist文件夹" cd /www/wwwroot/vue3Blog/view/dist echo "查看dist文件列表" ls 我的配置是删除旧文件,将ssh上传的文件进入移动到旧文件目录,再进行删除ssh上传的目录文件,建议打开SSH Server下的高级,勾选Verbose output in console,打印详细日志. 总结 这个配置自动化部署的方案可能有一些简单,例如gitlab没有使用Api Token而是选用账号密码登录,因为我这是一个比较小的项目,而且我的服务全都是私有化部署的,读者可以按照自己的想法进行配置,Gitlab占用的资源是非常大的,我的服务器是4C24G,所以非常够用,如果没有比较强一些的服务器,建议可以使用Gogs这类占用比较小的自己git仓库,也可以使用Github或者码云这些平台,同样可以. 写给自己看的vite配置 https://www.nanbk.com/archives/210/ 2023-06-05T16:19:00+08:00 几年前学习webpack时写过一篇文章,但是在几年后的今天,webpack也许不再适合一些大型的项目了,vite成为了更好的选择,有兴趣可以对别一下几年前的文章,vite的配置简单了许多.传送门 开始准备 mkdir vite-test cd vite-test npm i vite -D 此时我们创建好了vite项目的文件夹,并且使用npm安装好了vite,目录生成了node_modules和package.json还有package-lock.json文件夹,npm项目安装完成. 配置vite 在根目录中新建index.html和vite.config.js两个文件,vite.config.js相当于vue-cli创建的项目中的 vue.config.js 接下来先将html配置好. index.html <!DOCTYPE html> <html> <head> <meta charset='utf-8'> <meta http-equiv='X-UA-Compatible' content='IE=edge'> <title>Page Title</title> <meta name='viewport' content='width=device-width, initial-scale=1'> <!-- script`标签中的type="module"是告诉浏览器main.js是一个ES6模块,并且使用import和export语句来导入和导出模块. --> <script src='./main.js'></script> </head> <body> <div>这是一个vite项目</div> </body> </html> main.js 创建一个空的main.js文件 vite.config.js // 先将vite.config.js留空 packge.json //添加启动与打包命令 "scripts": { "dev": "vite", "build": "vite build" } 此时使用npm run dev命令运行该项目,启动成功后默认为5173端口,打开http://localhost:5173/显示html的内容,尝试修改下index.html自动热更新,不同于webpack需要配置热更新,vite默认开启热更新. css配置 vite 同时提供了对 .scss,.sass,.less,.styl 和 .stylus 文件的内置支持,只需安装相应的预处理器就可以了. # .less npm install less -D # .scss and .sass npm install sass -D # .styl and .stylus npm install stylus -D 选择你常用的css预处理器安装,根目录新建一个css文件,我用的是scss,在main.js中引入scss文件 import './index.scss' 此时浏览器可能会开始报错Uncaught SyntaxError: Cannot use import statement outside a module,这个错误通常出现在代码中使用了ES6的模块语法,但是浏览器并不支持它。要解决这个问题,可以将代码转换为CommonJS或其他浏览器支持的模块语法,我们需要将代码打包成浏览器可用的格式,不同于webpack需要添加一大堆的babel,在vite.config.js中我们只需要这样配置: esm: 'babel' 可能这样配置还是不行,为什么呢?因为我们在index.html中引入了main.js,而main.js中使用了import来引入index.scss文件,所以我们还需要在index.html中的script标签中添加type="module"来告诉浏览器这是一个ES6模块. PostCSS PostCSS是一个基于Node.js的CSS处理工具,可以用它来自动化处理CSS。它可以通过编写插件来扩展功能,支持使用JavaScript编写CSS,支持使用CSS未来的语法,如CSS Variables、CSS Grid等。PostCSS的主要功能是将CSS转换为更加兼容的CSS,例如自动添加浏览器前缀、压缩CSS等。它也可以用于优化CSS性能,例如减小CSS文件大小、合并CSS文件等。PostCSS已经成为了现代前端开发中不可或缺的工具之一,这里我介绍两个常用的PostCSS插件. postcss-preset-env postcss-preset-env是一个预设环境插件,包含高级 CSS 语法的降级、前缀补全等众多功能. npm i postcss-preset-env -D 在vite.config.js中配置 import postcssPresetEnv from 'postcss-preset-env' css: { postcss: { plugins: [postcssPresetEnv()] } } 随便在index.scss中写几个css样式,再查看网页的样式,你会发现,就算你没写css的浏览器兼容样式,css也会自动加上. postcss-px-to-viewport postcss-px-to-viewport一个对移动端不同设备进行布局适配的插件. npm install postcss-px-to-viewport -D 配置vite.config.js,将我们书写的 px 单位转为 vw 或 vh ,轻松地解决了适配问题. postcss: { plugins: [postcssPresetEnv(),postcssPxToViewport({ viewportWidth: 375 })] } scss配置 vite中配置SCSS预处理器的选项,其中additionalData是一个字符串,它会被添加到每个SCSS文件的顶部。这个字符串中使用了@use语法来导入一个名为index.scss的文件,as *表示将这个文件中的所有变量和函数都导入到当前文件的命名空间中。这个配置的作用是让每个SCSS文件都可以使用index.scss中定义的变量和函数,这样可以避免在每个SCSS文件中重复定义相同的变量和函数,提高了代码的复用性和可维护性,在一些主题配置中会经常使用到,例如更普遍的暗黑模式. css:{ scss: { additionalData: @use '@/theme/index.scss' as * ; } } // @我们还没配置,可以先使用相对路径替换项目中的文件路径 vue vite中使用vue单文件,只需要安装一些相应的插件并且配置即可. 安装@vitejs/plugin-vue npm i @vitejs/plugin-vue -D plugins: [vue()] ts vite 仅执行 .ts 文件的转译工作,不会执行任何类型检查。编辑器提示报错,也不会影响正常开发和生成环境打包,这样对我们的代码规范影响很大,所以我们需要通过下面两个插件进行约束: # 安装typescript依赖 npm install typescript -D # ts检查 npm install vite-plugin-checker -D 在vite.config.js中配置 plugins: [vue(),checker({ typescript: true })] 接下来我们在根目录下创建tsconfig.json,同时再创建tsconfig.base.json tsconfig.json配置: { "extends": "./tsconfig.base.json", // 检查规则延申文件 "compilerOptions": { // 配置引用别名 "paths": { "@/*": ["src/*"], "~/*": ["typings/*"] } }, "include": ["src", "typings", "auto-imports.d.ts"],// 检查目录 "exclude": ["node_modules", "**/dist"] // 排除检查目录 } tsconfig.base.json按需进行配置即可 { "files": [], "compilerOptions": { "target": "esnext", "module": "esnext", // 启用所有严格类型检查选项。 //启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。 "strict": true, // 允许编译器编译JS,JSX文件 "allowJs": false, // 允许在JS文件中报错,通常与allowJS一起使用 "checkJs": false, // 允许使用jsx "jsx": "preserve", "declaration": true, //移除注解 "removeComments": true, //不可以忽略any "noImplicitAny": false, //关闭 this 类型注解提示 "noImplicitThis": true, //null/undefined不能作为其他类型的子类型: //let a: number = null; //这里会报错. "strictNullChecks": true, //生成枚举的映射代码 "preserveConstEnums": true, //根目录 //输出目录 "outDir": "./ts-out-dir", //是否输出src2.js.map文件 "sourceMap": true, //变量定义了但是未使用 "noUnusedLocals": false, //是否允许把json文件当做模块进行解析 "resolveJsonModule": true, //和noUnusedLocals一样,针对func "noUnusedParameters": false, // 模块解析策略,ts默认用node的解析策略,即相对的方式导入 "moduleResolution": "node", //允许export=导出,由import from 导入 "esModuleInterop": true, //忽略所有的声明文件( *.d.ts)的类型检查。 "skipLibCheck": true, "baseUrl": ".", //指定默认读取的目录 //"typeRoots": ["./node_modules/@types/", "./types"], "lib": ["ES2018", "DOM"] } } 这样配置完成之后,如果有ts的错误,将会在页面进行显示并且指出错误的行数与错误原因. 环境变量 vite的环境变量和webpack的配置其实大差不差,只是获取环境变量的方法有一些变化. 根目录中创建几个.env文件,例如.env.dev和.env.bulid文件,分别在.env.dev和.env.bulid中添加VITE_APP_ENV = 'dev'和VITE_APP_ENV = 'build',区分开发和生成环境. 再修改packge.json中的script启动命令即可. "scripts": { "dev": "vite --mode dev", "build": "vite build --mode build" } 使用起来也非法的简单,只需要使用import.meta.env.VITE_APP_ENV即可获取到对于的环境变量参数,如果你有更多的环境需要配置,再新增所需的环境env文件和启动命令进行修改. vite配置 这里是一些vite的配置和优化 resolve.alias 常用的一些例如src等引用别名 import path from 'path' resolve: { alias: { '@': path.resolve(__dirname, './src') // 路径的别名 } } resolve.extensions 常用的一些文件拓展名省略 resolve: { extensions: ['.js', '.ts', '.json'], // 导入时省略的扩展名列表 alias: { '@': path.resolve(__dirname, './src') // 路径的别名 } } server.host 指定服务器监听的IP地址。默认是为localhost,只会监听本地的127.0.0.1。开发移动端或需要通过局域网访问是,可以将host设置为true或0.0.0.0,这样服务器就会监听所有地址,包括局域网和公网地址。 server: { host: true // or `0.0.0.0` } server.proxy 本地http等请求的代码,和webpack基本一致 proxy: { '/api': { target: 'http://192.168.1.1:9100', changeOrigin: true //rewrite: (path) => path.replace(/^\/api/, '') } } 还有一些例如server.port指定服务器端口,open启动时自动打开应用不一一列举了,可以在vite配置文档中查看. base 开发或生产环境服务的公共基础路径配置 base: '/home/' // 开发或生产环境服务的公共基础路径,配置后项目启动路径为`http://localhost:5173/home/` build.outdir 打包时的文件输出目录。默认是为dist,可以在dist被占用或有统一命名规范时,进行修改. build: { assetsDir: 'build' // 打包的文件的输出目录 } build.assetsDir 打包时的指定生成静态资源的目录。默认是为assets,根据自身情况进行调整. build: { assetsDir: 'static' // 静态资源目录 } build.minify 对生产环境的console和debugger进行移除. build: { minify: 'terser', terserOptions: { compress: { //生产环境时移除console drop_console: true, drop_debugger: true, }, }, } build.assetsInlineLimit 将较小的图片转成base64,通过设置阈值对图片进行base64格式转换. build: { assetsInlineLimit: 4096 // 图片大小,kb } 结语 以上就是我在使用vite时使用的一些配置,还有我学习vite时了解到的一些用法和插件,在一些中大型项目中,webpack已经比不上vite了,比如我司的一些项目,所有的webpack优化全部用上了,但是还是在启动与热更新上花的时间很长,vite的优点太多太多了,随便列举一个点,例如我司的中大型项目中,如果需要更换api环境与同时对接,我们的前端项目启动需要1-2分钟,修改vue.config.js后需要重启vue项目才能生效,而vite会帮你自动重启server服务,启动快,热更新快,这也导致了我不再关注webpack投入vite的怀抱的原因. 一个简单的九宫格抽奖代码 https://www.nanbk.com/archives/208/ 2020-12-19T23:12:00+08:00 最近双十一双十二都有抽奖活动,之前双十一的时候写了一个抽奖代码,双十二也继续用上了这个代码,正好双十二也结束了,抽空把抽奖代码给整理一下. 抽奖步骤. 我的这个抽奖大致分为以下五个步骤. 获取奖品列表数据. 获取到是否有抽奖次数. 有抽奖次数开始请求接口抽奖,没有抽奖次数提示没有抽奖次数. 获取到抽奖结果,开始抽奖动画. 结束抽奖,提示中奖信息. 开始拆分代码. 由于之前写的是微信小程序的抽奖代码,我把代码思路理了一下重新用vue来梳理一遍. 布局. 九宫格顾名思义就是有九个格子,一共八个奖品,中间是抽奖的按钮,先把九个格子的html写好,css代码我会放在文末. <div class="draw-bow"> <div class="lotteryButtom" style="" @click="startDraw"> <div class="ButtomMask" v-show="!lotteryButtom"></div> </div> <div v-for="(item, index) in prizeList" :key="item.id" :class="['Item',prizeIndex==index+1?'action':'']"> <!-- <image src="{{item.image}}"/> --> <div class="textCon"> <text>{{ item.name }}</text> </div> </div> </div> 参数变量. // 抽奖次数 lotteryNumber: 2, // 抽奖按钮控制 lotteryButtom: true, // 转盘索引(开始抽奖时高亮哪个奖品) prizeIndex: 0, // 抽中的奖品索引 prizeResult: null, // 抽中的奖品名称 prizeName: null, // 奖品图片 prizeImage: "", // 奖品列表 prizeList: [ {name:'一等奖',id:1}, {name:'二等奖',id:2}, {name:'三等奖',id:3}, {name:'四等奖',id:4}, {name:'五等奖',id:5}, {name:'六等奖',id:6}, {name:'七等奖',id:7}, {name:'八等奖',id:8}], // 活动规则弹窗 activityRules: false, // 奖品弹窗 prizePopup: false, timer:null//定时器 获取抽奖次数. 在页面加载前获取到是否有抽奖次数. created(){ //请求接口获取抽奖次数并且修改`lotteryNumber`(抽奖次数),与抽奖按钮控制`lotteryButtom` } 抽奖动画函数. 这个函数写的是抽奖动画的控制,每调用一次动画循环一圈,你所设置的圈数跑完之后赋值给指定的值函数匹配成功停止动画. //抽奖过程奖品切换 changePrize(){ // 开始切换class,动画开始 // --------------------- // class所在的索引增加 let prizeIndex=this.prizeIndex; prizeIndex++; // 做判断,判断索引是否大于奖品数量,如果是,清空,从第一个开始. this.prizeIndex=prizeIndex > this.prizeList.length ? 1 : prizeIndex; // 给结果赋值之后,开始做判断,抽奖将停止 if(this.prizeResult==this.prizeList[this.prizeIndex-1].id){ // 清除最后一个动画 clearInterval(this.timer); console.log('获得奖品:' + this.prizeName) // 显示奖品框 setTimeout(()=>{ this.prizePopup=true },700) // 判断是否还有抽奖机会 if(this.lotteryNumber!==0){ // 有抽奖机会抽奖按钮取消置灰 this.lotteryButtom=true // 清空定时器 this.timer=null; clearInterval(this.timer); } // 抽奖结束 // end. } } 开始抽奖. 开始抽奖时判断是否有抽奖次数,有次数将次数减去一次,开始请求接口,接口返回正确的参数后开始动画,这个时候先不赋值,先指定跑几圈动画,动画的快慢通过定时器调整,等指定的圈数跑完开始赋值停止动画. 模拟一个接口请求函数 // 模拟接口返回字段 lotteryDraw(){ return new Promise((resolve,reject)=>{ resolve({data:{ id:1, title:'二等奖', code:200 }}) }) } startDraw (){ // 判断按钮是否为灰色,灰色时不可再抽奖 if(!this.lotteryButtom){ alert('您没有抽奖资格哦') return } // 清空上一次抽奖 this.prizeResult=null, this.prizeName=null, this.prizeIndex=0 // 如果当前有抽奖机会 if(this.lotteryNumber>0){ // 抽奖按钮不可再点击 this.lotteryButtom=false // 请求到数据并且没有错误返回code==200 this.lotteryDraw().then((res)=>{ // 抽奖结果数据 var result = res.data; console.log(result) if(result.code==200){ // 次数减1 this.lotteryNumber=this.lotteryNumber-1 // 开始抽奖动画 // ----------- // 管你有没有定时器,先清除一遍. clearInterval(this.timer) // 正片开始,开始抽奖动画. // ----------- // 第一遍,来个快的. this.timer=setInterval(this.changePrize,80) console.log("第一遍") // 把第一遍抽奖动画关闭. // ----------- // 两秒后关闭第一个动画 setTimeout(()=>{ // 关闭上一个抽奖动画 // --------- clearInterval(this.timer) // 开始第二个动画 this.timer=setInterval(this.changePrize,160) console.log("第二遍") // 一秒后关闭第二个动画 setTimeout(()=>{ // 关闭上一个抽奖动画 clearInterval(this.timer) this.timer=setInterval(this.changePrize,300) console.log("第三遍") setTimeout(()=>{ // 给结果赋值(抽奖动画函数里面有判断,如果当前结果==抽奖的动画的索引,结束动画,抽奖结束.) this.prizeResult=result.id this.prizeName=result.title this.isType=true // end. },1000) },1000) },2000) } // 接口返回其它状态处理 else if(result.code==500){ } }) } // 没有抽机会,点了也没用 else { return } } 抽奖结束. 抽奖结束后在changePrize函数里面放一些控制弹窗进行奖品的提示,我这里就没有继续写了,这个思路其实非常简单. CSS代码 html, body { padding: 0; margin: 0; font-size: 16px; } .lotteryButtom { width: 95px; height: 95px; border-radius: 10px; background-size: cover; background-image: url("https://www.nanbk.com/usr/themes/Akina/images/avatar.png"); box-shadow: 0.5px 2px 2px 0px rgba(0, 0, 0, 0.8), 1.5px 1.5px 3px 0px rgba(255, 255, 255, 0.8), -1.5px -1.5px 3px 0px rgba(217, 148, 88, 0.5); display: flex; justify-content: center; position: absolute; left: 99px; top: 99px; .ButtomMask { width: 85px; height: 85px; border-radius: 10px; background-color: #9b2f10; opacity: 0.3; margin-top: 5px; } } .draw-bow { position: relative; margin: 20px auto; width: 293px; height: 293px; background-color: aquamarine; .Item { width: 95px; height: 95px; background: #9b2f10; box-shadow: 0.5px 2px 2px 0px rgba(0, 0, 0, 0.8), 1.5px 1.5px 3px 0px rgba(255, 255, 255, 0.8), -1.5px -1.5px 3px 0px rgba(217, 148, 88, 0.5); background-size: cover; border-radius: 10px; display: inline-block; } .Item:nth-child(2) { position: absolute; left: 0px; top: 0px; } .Item:nth-child(3) { position: absolute; left: 99px; top: 0px; } .Item:nth-child(4) { position: absolute; right: 0px; top: 0px; } .Item:nth-child(5) { position: absolute; right: 0px; top: 98px; } .Item:nth-child(6) { position: absolute; right: 0px; bottom: 0px; } .Item:nth-child(7) { position: absolute; right: 99px; bottom: 0px; } .Item:nth-child(8) { position: absolute; left: 0px; bottom: 0px; } .Item:nth-child(9) { position: absolute; left: 0px; top: 99px; } .action{ background-color: red !important; color: #ffffff; } } Vue SSR框架Nuxt使用小记 https://www.nanbk.com/archives/206/ 2020-10-23T23:27:00+08:00 最近一个老的项目需要重构,接触到Vue的SSR框架Nuxt,用法和vue有些相同,但是又有个别情况下不相同,项目结束后对此进行一个记录,通过几个点来记录我在开发中遇到的一些问题进行总结. 1. 搭建运行 基础搭建 基础搭建可以直接使用nuxt官方的脚手架生成,选择适合自己项目的一些配置集成. npx or yarn create-nuxt-app <项目名> 目录 Nuxt的目录基本和vue目录差别不大,不过Nuxt的路由是通过pages目录自动生成的,如果需要修改默认生成的路由需要在nuxt.config.js中的router配置中修改. 我们的重构项目因为url需要适配之前的url,所以我们所有的路由规则基本上都是动态的比如要访问xxx/动态参数/动态参数/动态参数.html需要将path设置成/xxx/:param1/:param2/:param3.html这样. 当然如果没有这么复杂的参数,可以直接将vue文件设置成_xx.vue,这样会自动生成xxx/动态参数path. 插件使用 nuxt插件需要在nuxt.config.js中注入,plugins中写入插件路径,并且通过挂载到Vue原型上. layouts layouts的使用方法和App.vue类似,比如多个页面使用同一个头部底部等等的,可以在layout添加layouts文件夹中的vue组件name,并且在内容区域使用<Nuxt>标签作为路由出口. 2. 生命周期 nuxt生命周期分为服务端生命周期和客户端生命周期,可以使用一张图流程图来细化理解. 注意,只有在上面绿色的生命周期中才能使用window,其它生命周期使用window都会报错. 3. seo 使用nuxt当然是为了更好的seo啦,在nuxt中设置seo的几个重要参数keywords、都在head函数中,包括一些需要在当前页引入的css文件或者js文件 head(){ return { title:'', meta:[{ hid: 'description', name: 'description', content: '' }, { hid: 'keywords', name: 'keywords', content: '' }, ], script:[{src:'',type:''}], link:[{rel:'',href:''}] } } 当然这里也可以动态设置,在asyncData请求中获取到的数据赋值到这里即可. 4. 注意事项 公共js、css等 公共的css、js还有seo标签可以在nuxt.config.js中的head中定义,如果页面没有定义head,会使用这里的seo标签信息和加载这里的css与js文件. vuex 一般的登录信息我们使用vue的时候会放在cookie或者LocalStorage中,每次获取通过vuex来获取,在axios中请求的时候放在header请求头中,但是Nuxt能不能这样做呢?答案是不能,因为服务端生命周期中访问不到window,那就是说我们无法读取到cookie或者LocalStorage中的 数据了. 怎么解决这个问题?我们使用了一个cookie插件cookie-universal-nuxt. 首先安装cookie-universal-nuxt npm i cookie-universal-nuxt -S 将插件添加到nuxt.config.js中的modules中 modules: [ 'cookie-universal-nuxt', ] 然后就可以在服务端的生命周期钩子中使用cookie了. context.$cookies.set('token') context.$cookies.get('token') context.$cookies.remove('token') 5. 遇到的问题 组件生成多次 遇到一个性能问题,Nuxt在某种情况下会生成两个</Nuxt>组件,导致出现多个页面html,但是这个问题不会导致页面出现问题,只是在Vue devtools插件中发现的,github上提出了issuse,暂时未解决. 更新,新版本已经解决了该问题. Axios的使用 正常情况下,我们会将每个模块的api分成不同的api文件,方便维护,但是在Nuxt中我们怎样才能这样做呢?因为我们在独立的api/xxx.js文件中访问不到全局的$axios插件. 这里我给出的解决方法是,在请求接口的时候通过context将$axios当做参数传递. /** * @description: 请求时调用的参数 * @param { this } context vue实例 * @param { xxx } data 请求需要的参数 * @return {*} */ export function xxxxx(context,data){ // const { phone, action, yzm_ticket, yzm_ticket }=data return context.$axios.post(`你的接口url`,{...data}) } async asyncData (content){ //请求接口,将vue实例和请求参数传递 xxxxx(content,data) } 然后就可以继续按模块分类接口了. BUS的使用 在我们的业务场景中,需要在多个页面调用到layouts组件的函数,vuex明显不合适,所以我使用了Bus来解决这个问题. nuxt中使用Bus和vue的区别不大,基本都是使用on()监听,emit触发事件即可. 首先我们需要在plugins中新建一个js文件,并且将$bus添加到vue的原型上,最后创建监听触发关闭三个事件. import Vue from "vue"; const bus = new Vue(); Vue.prototype.$bus = { on(...event) { bus.$on(...event); }, off(...event) { bus.$off(...event); }, emit(...event) { bus.$emit(...event); } } 当然最后别忘了在nuxt.config.js中添加插件,plugins:['~/plugins/bus'] 6. 有哪些缺陷 启动时间过长 因为这次重构的项目东西比较多,大概在我们重构第二个模块的时候,启动速度慢了下来,最后所有模块重构的差不多的时候,每次启动都需要花费非常多的时候. 开发体验 和启动时间一样,模块多起来的时候,每次修改热更新都需要花费非常多的时间,基本上每次保存代码热更新的时候都可以喝上好几口水了,这个给我们的开发体验是非常差的. 错误页面 Nuxt的报错会导致整个页面不可访问,比如有一个变量报了一个错误,会让整个页面无法渲染完成,导致页面出错. 7. 总结 什么样的框架 Nuxt确实是一款非常不错的SSR框架,如果是熟悉Vue的人用来开发基本上可以一边看文档一边上手写代码,seo优化非常的友好的,性能上也比传统的前后端不分离的要好很多,我们重构后的项目性能提升非常之大. 适合什么场景下使用 我个人觉得这个框架适合一些页面模块不是非常多的项目使用,因为页面模块多起来的话,开发体验会非常的不友好,比我前面提到的启动卡顿,开发也非常的卡顿. 建议 国内使用这个框架的确实很多,但是在开发途中会遇到一些问题找不到,建议多上Github去了解一下,看看别人提出的问题有没有类似的,这样可以减少很多搜索问题的时间,也可以快速的解决问题,并且开发人员回馈速度也是很快的. vue自定义指令实现全局按钮权限控制. https://www.nanbk.com/archives/198/ 2020-08-19T21:34:00+08:00 在博客的上一篇文章写道了动态路由权限控制,网上寻找一番没有找到权限按钮控制,我按照自己的思路实现了通过自定义指令控制权限按钮. 按钮权限实现方法我这边使用了一个比较简单的方法,大部分教程都是使用的v-if来控制按钮的权限,但是这样需要在每个按钮都加上v-if判断,这样如果某个权限id有变化,需要重新检索一遍按钮id,使用我这个方法就不需要了,只需要加上自定义指令即可. 如果你用了动态路由,需要后端返回的路由中添加一些判断参数. // 返回的路由meta字段中添加Authority数组,这个用来判断是否有该按钮的权限 // ps:动态路由中只有meta字段能过获取到参数. { { "id": 1, "children": [ { "id": 2, "meta": { "Authority": [ "导入学员档案", "下载批量导入模板", "创建学员档案", "编辑", "导出" ] } } ] } } 新建自定义指令文件,最后引入到main.js中. export default (Vue)=>{ //自定义指令,全局按钮权限控制 Vue.directive("has",{ inserted:function(el, binding, vnode, oldVnode){ //判断是否需要权限,并且查看按钮权限是否匹配,如果不匹配删除按钮.(el是dom元素,需要根据自己项目变更.) if(!vnode.context.$route.meta.Authority||!vnode.context.$route.meta.Authority.includes(el.children[0].innerText?el.children[0].innerText:el.children[1].innerText)){ el.parentNode.removeChild(el); } } }) } main.js注册全局组件,在需要做权限控制的按钮添加v-has指令. //引入自定义指令文件 import directives from '@/utils/directives' //注册指令 Vue.use(directives) //使用自定义指令 <button v-has></button> 小结 不一定所有的权限都可以使用自定义指令控制,建议存储一个按钮权限数组,使用v-if="[].includes()"来判断是否存在. 我这可能并不是最优的解决方法,但是在我的这个项目中我觉得是比较不错的解决方案,有更好的方案,欢迎指出. vue动态路由详解. https://www.nanbk.com/archives/191/ 2020-08-17T20:27:00+08:00 使用vue开发一些功能时,需要动态的判断是否有权限进入该路由,特别是一些后台系统,如果单一的只有几个角色的权限配置还好,如果是每个权限都需要配置,这个时候就需要使用到vue的动态路由了. 一般的动态路由,有固定的权限的,可以在前端存储一份权限路由,可以移步 vue-element-admin,比较详细. 所有路由(不包括404等通用路由)做动态路由,使用vue-element-admin的那种做法是不可取的,因为每个权限都需要可以控制. 我们需要后端的配合,如果后端给的数据格式不合适,你需要做大量的操作来洗数据,数据格式可以按照这种: [ { "id": 0, "children": [ { "id": 1, "children": [] } ] } ] 获取到数据之后我们需要在路由拦截器中判断是否登录,如果登录获取权限进行动态路由添加. router.beforeEach(async(to, from, next) => { //判断token是否存在的函数 const hasToken = getToken() //token存在,已经登录 if (hasToken) { //判断路由是否已经保存了 if(!store.getters.addRouter.length){ //请求路由的api getLoginUserPermission() .then((res)=>{ //拷贝路由数据 const data=JSON.stringify(res.data.data) //路由筛选函数 const addRoute=filterAsyncRouter(res.data.data) //如果需要二次筛选 addRoute.forEach((Item,index)=>{ Item.children.forEach((Items,indexs)=>{ delete addRoute[index].children[indexs].children }) }) // 存储路由到vuex中 store.dispatch('user/AddRouter', res.data.data) // 动态路由添加 router.addRoutes(addRoute) //添加完动态路由再将404跳转添加 router.addRoutes([{ path: '*', redirect: '/404', hidden: true }]) next({ ...to, replace: true }) }) } next() }) filterAsyncRouter 路由筛选函数. const _import = require('./_import_' + process.env.NODE_ENV)//获取组件的方法 //Layout 是架构组件,不在后台返回,在文件里单独引入 import Layout from '@/layout/layout.vue' function filterAsyncRouter(asyncRouterMap) { //遍历后台传来的路由字符串,转换为组件对象 const accessedRouters = asyncRouterMap.filter(route => { if (route.component) { if (route.component === 'Layout') {//Layout组件特殊处理 route.component = Layout } else { route.component = _import(route.component) } } if (route.children && route.children.length) { route.children = filterAsyncRouter(route.children) } return true }) return accessedRouters } export default filterAsyncRouter 区分生产环境与开发环境. 新建_import_development.js(开发环境) module.exports = (file) => { // 路由懒加载 return () => import(`@/view/${viewPath}`) } 新建_import_production.js(生产环境) // 生产环境使用懒加载打包后会找不到js文件,有知道的朋友可以下方留言告诉我一下. module.exports = (file) => { return (resolve) => require(['@/views/' + file + '.vue'], resolve) } 动态路由常见问题. (404问题):404路由不能直接加载到路由问题,动态路由添加时无法匹配到路由,所以都会跳转到404路由,所以需要在添加完动态路由后再添加404路由. 刷新后页面空白问题,需要在路由拦截器中添加router.addRoutes(newRouter)后需要next({ ...to, replace: true }),具体原因还没找到,后续找到会在此记录. 文章到这,动态路由已经完成了. SPA预渲染 https://www.nanbk.com/archives/182/ 2020-06-01T22:23:00+08:00 在一般的SPA项目中,我们在最后打包的时候只会生成一个HTML,JS与CSS文件,或许你会采用一些方法,比如公共文件拆分,路由懒加载等等生成多个文件,但是也无法从根本上解决这个问题,所有的资源还是通过JS动态的生成渲染的。 所以,所谓的预渲染就是在单页应用中,将用户交互不多,同时需要SEO的页面单独提取出来的一种方法,提取出来的就是一个HTML文件。 如何做预渲染. 使用webpack插件prerender-spa-plugin开启预渲染模式. 使用过node插件superagent做过爬虫的应该看到prerender会比较熟悉,没错,模式还是那个模式. 安装prerender-spa-plugin npm i prerender-spa-plugin --save. 直接在webpack插件中添加prerender-spa-plugin. //引入插件 const PrerenderSPAPlugin = require('prerender-spa-plugin') const Renderer = PrerenderSPAPlugin.PuppeteerRenderer //开启预渲染 process.env.NODE_ENV=='production'? new PrerenderSPAPlugin({ staticDir: path.join(__dirname, '../build'), routes: ['/', '/home',''/tags/vue''], renderer: new Renderer({ renderAfterTime: 5000 }), server: { proxy: { "/api": { target: "https://www.nanbk.com/api/", secure: false, changeOrigin: true, pathRewrite: { "^/api": "" } } } } }):null 我这种配置不会打开chromium浏览器,但也会将页面内容获取到. 解读一下插件配置. process.env.NODE_ENV=='production'判断是否为生产环境,我们只在打包时开启预渲染. staticDir:打包目录. routes:预渲染路由. renderAfterTime:5000ms后去渲染. proxy:预渲染代理接口. 具体的配置可以查看传送门 上线配置. 我使用的Nginx. location / { root /www/build; index index.html; try_files $uri $uri/ /index.html; } 讲一下我所了解的利弊. 由于SPA项目影响渲染速度,我们把部分页面渲染成静态页面,首屏加速速度回更快. 预渲染的模式seo如果是动态数据比不过服务端渲染,静态页面的话seo是一致的. 对于一般的页面,比如静态的官网,不想使用spa框架的ssr框架,使用预渲染是一个非常不错的选择. 动态数据还是建议使用服务端渲染,或者ssr框架,预渲染路由不可能一个个的去/a/1 /a/2这样去匹配爬取,这是非常不明智的. Vue3.0初体验. https://www.nanbk.com/archives/164/ 2020-04-17T23:02:00+08:00 2020/4/17凌晨 4 点左右,vue-next v3.0.0-beta.1 版本发布,做为使用了接近两年vue的我来说,当然是在第一时间就上手体验一波. vue-next v3.0.0-beta.1发布的内容有: vue: Beta vue-router: Alpha vuex: Alpha vue-class-component: Alpha vue-cli: Experimental support via vue-cli-plugin-vue-next eslint-plugin-vue: Alpha vue-test-utils: Alpha vue-devtools: WIP jsx: WIP vue3.0项目初始化 查看自己的cli版本vue -V,如果不是最新版,需要安装最新版本的vue-cli npm install -g @vue/cli 初始化vue项目,进入vue-cli Ui控制台创建 勾选基本的Vuex,Vue-Router,CSS Pre-processors,Linter / Formatter等. (还没完,先别急着启动项目)目前创建 Vue 3.0 项目需要通过插件升级的方式来实现,vue-cli 还没有直接支持,我们进入项目目录,并输入以下指令: vue add vue-next 执行这一段后会: 安装 Vue 3.0 依赖 更新 Vue 3.0 webpack loader 配置,使其能够支持 .vue 文件构建(这点非常重要) 创建 Vue 3.0 的模板代码 自动将代码中的 Vue Router 和 Vuex 升级到 4.0 版本,如果未安装则不会升级 自动生成 Vue Router 和 Vuex 模板代码 尝试一下vue3.0的新特性 Vue 3.0 中初始化状态通过 setup 方法 import { ref } from 'vue' export default{ setup (){ } } 定义状态ref与事件 <template> <div> <p>count:{{count}}</p> <button @click="add">加</button> </div> </template> import { ref } from 'vue' export default{ setup (){ // 创建count变量 const count = ref(0) // 创建一个方法 const add = () => { count.value++ } return { count, add } } } 计算属性与监听器 import { ref,computed } from 'vue' export default{ setup (){ // 创建count变量 const count = ref(0) // 创建一个方法 const add = () => { count.value++ } // 计算属性 const GetCount = computed(() => count.value * 2) // 监听 watch(() => count.value, val => { console.log(val) }) return { count, add, GetCount } } } ctx获取上下文 通过getCurrentInstance方法可以获取上下文,通过ctx.router是路由实例,ctx.store获取vuex实例等等. import { getCurrentInstance } from 'vue' export default { setup () { // ctx获取上下文,路由信息,vuex等 const { ctx } = getCurrentInstance() } } 总结一下 vue3.0有一些与react hook非常的相似,如果有使用过react hook应该不会非常的诧异. 目前只是发布了beta版,并不适用在项目中. 适配vue3.0的vuex与vue-router都还在开发中,现在的vuex与vue-router改动并不大,适配vue3.0的vuex与vue-router可能会有一些useRoute,useStore类似react的,这些需要等正式版本发布才能揭晓. 非常期待vue3.0的正式版本,应该又是一种全新的体验吧. 写给自己看的Webpack配置 https://www.nanbk.com/archives/149/ 2020-01-16T17:24:00+08:00 最近有复习了一遍webpack,想着把webpack配置与优化记录下来,忘记的时候可以来翻一翻. webpack核心 wentry: 入口 output: 输出 loader: 模块转换器,用于把模块原内容按照需求转换成新内容 插件(plugins): 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你* 想要做的事情ebpack核心 exclude排除目录 rules是一个数组 loader需要配置在module.rules test是匹配规格 use 字段有几种写法 可以是一个字符串 use: 'babel-loader' use 字段可以是一个数组,例如处理CSS文件是,use: ['style-loader', 'css-loader'] use 数组的每一项既可以是字符串也可以是一个对象,当我们需要在webpack 的配置文件中对 loader 进行配置,就需要将其编写为一个对象,并且在此对象的 options 字段中进行配置. mode 配置项,告知 webpack 使用相应模式的内置优化 mode 配置项,支持以下两个配置: development:将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPlugin 和 NamedModulesPlugin production:将 process.env.NODE_ENV 的值设置为 production,启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin 准备工作 npm install -S===npm install --save1 npm install -D===npm install --save-dev –save会把依赖包名称添加到package.json文件dependencies键下,–save-dev则添加到package.json文件devDependencies键下 dependencies与devDependencies的区别 devDependencies 里面的插件只用于开发环境,不用于生产环境,而 dependencies 是需要发布到生产环境的。 创建项目 mkdir webpack cd webpack npm init -y 安装webpack npm install webpack webpack-cli -D 创建src目录,在src目录下新建一个index.js 创建public目录,在public目录下新建index.html 创建assets目录,在assets目录下新建css、images目录,分别存放我们的css与图片文件. 创建webpack配置文件webpack.config.js 配置入口与出口打包模式 //入口配置 //入口配置 entry: './src/index.js', //出口配置 output: { path: path.resolve(__dirname, 'dist'), filename: 'static/js/[name]_[hash:6].[chunk].js', publicPath: '/' }, //打包模式 mode: 'development', 设置热更新 安装webpack-dev-server npm install webpack-dev-server cross-env -D package.json写入 "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server", "build": "cross-env NODE_ENV=production webpack" } 在webpack.config.js中写入 module.exports = { //... devServer: { port: '3000', //默认是8080 quiet: false, //默认不启用 inline: true, //默认开启 inline 模式,如果设置为false,开启 iframe 模式 stats: "errors-only", //终端仅打印 error overlay: false, //默认不启用 clientLogLevel: "silent", //日志等级 compress: true //是否启用 gzip 压缩 } } 每次打包删除dist文件夹 安装npm install clean-webpack-plugin -D,引入 const { CleanWebpackPlugin } = require('clean-webpack-plugin') //每次打包删除dist文件 plugins:[ new CleanWebpackPlugin() ] Webpack配置html HtmlWebpackPlugin插件. 安装npm install html-webpack-plugin -D 引入webpack中const HtmlWebpackPlugin=require('html-webpack-plugin') 使用 plugins:[ new HtmlWebpackPlugin({ //html文件路径 template: './src/public/index.html', filename: 'index.html', //打包后的文件名 minify: { removeAttributeQuotes: false, //是否删除属性的双引号 collapseWhitespace: false, //是否折叠空白 }, //chunks:['index']//引用的js文件 // hash: true //是否加上hash,默认是 false }) ] 多个html配置,需要添加多个HtmlWebpackPlugin``template与filename以及chunks,同时需要配置多个入口文件entry 更多配置查看文档 传送门 Webpack配置css 如果使用scss需要安装node-sass与sass-loader,使用less安装less与less-loader. webpack 不能直接处理 css,需要借助 loader。如果是 .css,我们需要的 loader 通常有: style-loader、css-loader,考虑到兼容性问题,还需要 postcss-loader,而如果是 less 或者是 sass 的话,还需要 less-loader 和 sass-loader,这里配置一下 scss 和 css 文件(less 的话,使用 less-loader即可) style-loader 动态创建 style 标签,将 css 插入到 head 中. css-loader 负责处理 @import 等语句. postcss-loader 和 autoprefixer,自动生成浏览器兼容性前缀. sass-loader 负责处理编译 .scss 文件,将其转为 css. mini-css-extract-plugin抽离css. optimize-css-assets-webpack-plugin压缩css. 安装css-loader postcss-loader node-sass sass-loader autoprefixer style-loader npm install css-loader postcss-loader node-sass sass-loader autoprefixer style-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin -D webpack.config.js配置 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin'); rules:[ // scss css文件处理 { test: /\.(sc|c$)ss$/, use:[MiniCssExtractPlugin.loader,'css-loader',{ loader:'postcss-loader', options:{ plugins:function(){ return [ require('autoprefixer')({ "overrideBrowserslist": [ ">0.25%", "not dead"] }) ] } } },'sass-loader'], exclude:/node_modules/ //排除 node_modules 目录 }, ], plugins:[ //css文件压缩 new OptimizeCssPlugin(), //抽离css new MiniCssExtractPlugin({ filename: '[name]_[hash:6].css', publicPath: '/assets/css'//如果需要单独的css目录,并且使用了url-loader,此项必须填写. }) ] 如果需要编写兼容低版本的浏览器,可以在postcss-loader添加配置. 建议新建.browserslistrc文件,或在package.json中配置 # 注释是这样写的,以#号开头 #这是browserslistrc文件的通用配置 last 1 version, not dead > 0.2% 修改plugins plugins:[ { test: /\.(sc|c$)ss$/,//匹配规则 use:[MiniCssExtractPlugin.loader,'css-loader',{ loader:'postcss-loader', options:{ plugins:function(){ return [ require('autoprefixer')() ] } } },'sass-loader'], exclude:/node_modules/ } ] webpack将js转义成低版本(babel) 先安装和配置babel npm install babel-loader -D npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -D npm install @babel/runtime @babel/runtime-corejs3 *webpack.config.js写入配置 module.exports = { module: { rules: [ { test: /\.js?$/, use: ['babel-loader'], exclude: /node_modules/ //排除 node_modules 目录 } ] } } 创建.babelrc文件 写入配置 { "presets": ["@babel/preset-env"], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] } 在webpack中配置babel rules: [ { test: /\.jsx?$/, use: { loader: 'babel-loader', options: { presets: ["@babel/preset-env"], plugins: [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] } }, exclude: /node_modules/ } ] 如果使用了OptimizeCssPlugin压缩css,那么js将不再会被压缩,解决方法如下 安装UglifyjsPlugin npm install uglifyjs-webpack-plugin -D plugins:[ new UglifyjsPlugin({ // 使用缓存 cache: true }) ] 图片文件处理 安装url-loader与file-loader npm install url-loader file-loader -D webapck中添加配置 rules: [ { test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/, use: [{ loader: 'url-loader', options: { limit: 10240, //10k esModule: false, //outputPath: 'assets/images', //将图片打包到该目录下 name: 'assets/images/[name]_[hash:6].[ext]' //文件名添加hash值 } }], exclude: /node_modules/ //排除 node_modules 目录 } ] html里的本地图片处理 安装html-withimg-loadernpm npm install html-withimg-loader -D 在webpack.config.js中配置 rules: [ { test: /\.(htm|html)$/i, use: ['html-withimg-loader'] } ] 按需加载 webpack内置了强大的分割代码的功能可以实现按需加载,需要使用 import() 语法,import() 语法,需要 @babel/plugin-syntax-dynamic-import 的插件支持,@babel/preset-env 预设中已经包含了 @babel/plugin-syntax-dynamic-import,配置了@babel/preset-env就不需要再配置@babel/plugin-syntax-dynamic-import了. 定义环境变量 通过webpack内置插件DefinePlugin来区分开发环境与生产环境,DefinePlugin在编译的时候创建一个全局变量,可以在开发环境与生产环节进行不同的行为. webpack.config.js中添加 new webpack.DefinePlugin({ DEV: JSON.stringify('dev'), //字符串 FLAG: 'true' //FLAG 是个布尔类型 }) 区分生产开发环境 DEV=='dev'?'开发环境':'生成环境' webpack配置解决跨域问题 该跨域只能解决开发环境跨域问题,生产环节需要使用Nginx等web服务器配置,或后端配置跨域. devServer: { proxy: { "/api": "http://localhost:4000" } } Nginx开启gzip开启极速体验 https://www.nanbk.com/archives/21/ 2019-08-20T11:02:00+08:00 借助webpack插件compression-webpack-plugin开启gizp压缩. 上周把代码放到生产环境的时候发现首屏加载非常的慢,明明使用了webpack代码分割,但是打开速度还是需要6-7秒的时间,大部分依赖都使用了按需引入,查看服务器带宽的时候才发现,我的天,每秒只有200k左右的下载速度. 查看Nginx配置的时候,果然,缓存和gizp都没有开,配置一番后,速度果然快了很多,至于是多少呢?大概2秒左右就能加载出首屏,怎么配置? 首先我们的代码需要在打包的时候将每一份文件压缩成zip文件,这里需要借助一个webpack插件便于我们打包压缩. 先安装插件: npm i -D compression-webpack-plugin 这个项目是使用vue-cli3做的,所以需要在vue.config.js里面使用,如果是cli2.0的项目还是需要在webpack.config.js添加配置. webpack.config.js引入compression-webpack-plugin const CompressionPlugin = require("compression-webpack-plugin") 在plugins添加:(如果需要更具体的配置,建议查看webpack文档) new CompressionPlugin({ test: /\.js$|\.html$|\.css/, threshold: 10240, deleteOriginalAssets: false }) 然后build一下,你会发现你的css文件和js下多了很多个名字一样的压缩包. 像我的这个react博客也同样使用了gzip压缩. 方法都是一样的,先安装,在webpack.config.js中引入compression-webpack-plugin 然后再: new CompressionPlugin({ test: /\.js$|\.html$|\.css/, threshold: 10240, deleteOriginalAssets: false }) 然后再build. 顺便贴一下我的nginx配置 在nginx.conf中的http添加如下代码: gzip on; # 启用gzip压缩的最小文件,小于设置值的文件将不会压缩 gzip_min_length 200; # gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间 gzip_comp_level 2; # 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。 gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 是否在http header中添加Vary: Accept-Encoding,建议开启 gzip_vary on; # 禁用IE 6 gzip gzip_disable "MSIE [1-6]\."; 重载配置,你会发现你的网站加载速度快了很多有没有,好了,快使用nginx开启你的极速之旅吧! This page loaded in 0.000542 seconds