记一次webpack打包

记一次webpack打包前言 公司的一个公众号要做一个H5的活动. 很简单的两个页面, 写完之后, 我想要不要去做一下压缩, 还是直接放上去就好了, 后面一想, 还是做下压缩吧, 正好重新学习下webpack, 以前用webpack 都是人家写好的手脚架, 拿来直接用的, 自己改改, 没啥问题, 但是要自己重新搭一套, 好像也不太会, 所以趁这次机会实验一下.项目详情 由于只是一个小的活动页, 只有三个页面, 所以, 开始写的时候, 框架只采用了 zepto.js, 后面需要一个截屏的功能, 所有又用了 html2canvas. 并且最坑的是,我再开发时并没有采用webpack去开发, 最简单的方式去开发, 搭配nginx.目录结构: less 用less写样式, 实时编译cssjs 逻辑控制libs 用来放第三方库config 有两个文件, utils.js 和 api.jsimage 用来放图片html 文件放在最外层开始进行 webpack 配置 npm init 进行初始化npm install --save-dev webpack webapck-cli (注意: webpack版本使用的是 4.39.2 )根据文档在根目录下添加 webpack.config.js 配置文件在 package.json 中 添加 "build": "webpack --config webpack.config.js"配置入口文件: 在这里由于我有三个页面, 所以配置了三个入口文件, 对应我的三个js文件并且由于加入了webpack的原因, 新增了 src 目录, 将 js 放在src下面配置出口文件: ...

August 21, 2019 · 1 min · jiezi

如何解决npm-run-build后页面空白

欢迎关注前端小讴的github,阅读更多原创技术文章问题一:assetsPublicPath配置错误解决办法:打开config/index.js文件 build:{ // assetsPublicPath: '/' assetsPublicPath: './'} 问题二:路由history模式配置有误解决办法:关闭路由historym模式 export default new Router({ // mode: 'history', // require service support scrollBehavior: () => ({ y: 0 }), routes: constantRouterMap})

July 10, 2019 · 1 min · jiezi

将python打包为exe可执行文件Pyinstaller工具

python是脚本语言,如果你在开发机器上执行当然会很方便,可是如果你帮他人写了个脚本,想在他人机器上运行,可能会遇到点困难。 为了让脚本在他人机器上顺利执行,你首先要帮他安装python解释器,如果你import了一些库比如pandas,那这个也需要安装,甚至相关的依赖也需要安装。这个时间成本显然是很大的,这部分工作也很枯燥,甚至会影响工作积极性,因此花一点时间,学会如何将python脚本打包为.exe文件,无需再安装各种开发环境和依赖库,是一件一劳永逸的事情。 现在,主流的工具有Pyinstaller、cz_Freeze、py2exe,本文只介绍Pyinstaller的使用。 准备工作开发环境和工具: python 3.7Pyinstaller 3.4windows 10Pyinstaller可以支持Python 2.7 和3.4—3.7。*要打包的文件myscript.py源码:import pandas as pddf = pd.read_excel('D:/account.xls')result = dr.dropna()result.to_excel('D:/result.xls')该源码包含pandas, xlrd, xlwt等库在windows上安装Pyinstaller根据Pyinstaller官方表述,在windows上安装Pyinstaller需要先安装pywin32 或 pypiwin32, 否则待会儿转换时,会提示无法找到win32com模块的错误,虽然报错缺win32com,但你要清楚缺的其实是pywin32 或 pypiwin32。先用下面pip命令查看自己是否已安装该模块:pip list如果没有找到,在pywin32下载地址下载对应版本的pywin32,或者直接用下面命令安装pypiwin32:pip install pypiwin32最后安装Pyinstaller:pip install pyinstaller开始打包可以使用下面命令打包pyinstaller myscript.py该命令执行后, pyinstaller会分析myscript.py文件,并且在脚本script.py所在文件夹中: 生成myscripy.spec创建文件夹build在build文件夹中生成日志文件和运行所需文件创建dist文件夹在dist文件夹中创建可执行文件夹myscript,在myscript文件夹中,就可以找到myscript.exe可执行文件上面命令生成了很多依赖文件,如果想打包为独立单一的.exe文件,使用-F参数pyinstaller -F myscript.py默认生成的执行文件执行时,会弹出一个命令行的窗口,如果不需要弹出,可以再加个-w参数pyinstaller -F -w myscript.py遇到的问题打包过程中难免会遇到问题,一般都会遇到缺少模块的问题,比如,在对myscript.py打包时会报错ModuleNotFoundError,缺少xlrd, xlwt等模块,一般来说可以按照下面三个步骤进行解决: Pyinstalled会从本地的环境路径下找需要的模块,例如python的包都放在site-packages目录下,因此,Pyinstalled可以直接从该目录下获取需要的模块。如果本地也缺少该模块,先通过pip命令安装。例如:pip install xlrdpip install xlwt如果你需要的模块不在site-packages,而在你指定的目录下,那打包时你可以通过-P DIR参数指定,其中DIR为你需要的模块路径:pyinstaller -F -w -p DIR myscript.py-p命令允许指定多个模块路径,可以用:分隔,或者多次使用这个参数,像这样:pyinstaller -p DIR1:DIR2:DIR3 myscript.py或pyinstaller -p DIR1 -p DIR2 -p DIR3 myscripy.py有时候并不是找不到模块,而是代码中有些模块是隐含导入的,这样的话就需要指出这些模块,才能正确的打包,可以在命令行打包时使用参数--hidden-import MODULENAME指定模块名,或者可以在打包后生成的myscript.spec文件中修改,该文件中有个参数hiddenimports=[] 配置为: hiddenimports=['cython','sklearn','sklearn.ensemble','sklearn.tree._utils','scipy._lib.messagestream'] 然后再运行以下命令 pyinstaller myscript.spec到此本片文章结束。 如果遇到什么问题,欢迎大家和我交流。如有不对之处,还望指正。 ...

July 1, 2019 · 1 min · jiezi

关于vue项目打包后背景图路径找不到问题

1、情况一:使用MiniCssExtractPlugin插件 MiniCssExtractPlugin打包后背景图片路径404问题,解决方法: 2、情况二:使用ExtractTextPlugin插件 ExtractTextPlugin打包后背景图片路径404问题,解决方法:

May 5, 2019 · 1 min · jiezi

electron打包踩过的坑总结

vue-electron 执行npm run build时,在build的时候会因为下载远程打包所需文件而超时,然后根据错误一步一步就行手动安装相应的文件。虽然在网上参考了很多相关方法,最终还是失败,然后屡次尝试后,终于成功了。附上elelctron相关的淘宝镜像地址:https://npm.taobao.org/mirror…step1:npm run build后,第一次报错需要下载 electron-v2.0.18-win32-x64.zip(我这里是需要该版本的文件,根据自己的错误信息,来选择对应的版本下载即可),在镜像中选取该版本号 2.0.18,点击进入,并选择下载 electron-v2.0.18-win32-x64.zip 和 SHASUMS256.txt, 下载完成后,将SHASUMS256.txt文件改成 SHASUMS256.txt-2.0.18,然后将两个文件拷入如图位置:step2:完成step1后,继续npm run build,发现又有文件下载失败 winCodeSign-2.4.0(我这里是需要该版本的文件,根据自己的错误信息,来选择对应的版本下载即可),然后自己手动下载https://github.com/electron-u…,这里下载的是Source code(zip),速度快,下载完成后解压,拷贝如图位置所有文件:, 拷贝至如图位置:step3:完成step2后,继续npm run build,发现又有文件下载失败 nsis-3.0.3.2(同上),然后自己手动下载https://github.com/electron-u…,同上,下载完成后解压,拷贝如图位置所有文件:, 拷贝至如图位置:step4:完成step3后,继续npm run build,发现又有文件下载失败 nsis-resources-3.3.0,但是按照上面的方法操作,最后还是会报错,然后我尝试,用step3中下载解压后的这个nsis-3.0.3.2版本试试,拷贝如图位置所有文件: 拷贝至如图位置:至此,我们一共进行了四次拷贝操作,完成以上四步操作后,运行npm run build,不一会儿就能打包成功,得到你的第一个exe版本。

March 17, 2019 · 1 min · jiezi

iOS新手用swift写一个macos打包工具 一键打包到指定位置

使用dmg安装macos app打包出的app运行如下图,使用磁盘压缩成dmg,直接打开package.dmg即可配置完毕后点击start运行打包脚本,生成ipa到指定目录该项目用swift开发,项目和dmg保存在https://github.com/gwh111/tes…流程解析概述整个流程就是,通过recoverAndSet()函数恢复之前保存数据,start()检查路径后会替换内部package.sh的动态路径,然后起一个线程创建Process(),通过Pipe()监控脚本执行输出,捕获异常1.recoverAndSet()通过UserDefaults简单地记住上次打包的路径,下次写了新代码后即可点击start立即打包恢复时把值传给控件func recoverAndSet() { let objs:[Any]=[projectPath,projectName,exportOptionsPath,ipaPath] let names:[NSString]=[“projectPath”,“projectName”,“exportOptionsPath”,“ipaPath”] for i in 0…3{ print(i) let key=names[i] let obj=objs[i] as! NSTextField let v=UserDefaults.standard.value(forKey: key as String) if (v == nil){ continue } obj.stringValue=(v as? String)! } let ps=UserDefaults.standard.value(forKey: “projectName” as String) if (ps==nil){ }else{ projectName.stringValue=(ps as? String)!; } let dr=UserDefaults.standard.value(forKey: “debugRelease”) if (dr==nil){ }else{ debugRelease.selectedSegment=dr as! Int; } debugRelease.action = #selector(segmentControlChanged(segmentControl:)) }2.selectPath()通过NSOpenPanel()创建打开文档面板对象,选择文件目录,而不是手动输入通常项目路径名和项目名称是一致的,这里使用了path.components(separatedBy:"/")将路径分割自动取工程名@IBAction func selectPath(_ sender: NSButton) { let tag=sender.tag print(tag) // 1. 创建打开文档面板对象 let openPanel = NSOpenPanel() // 2. 设置确认按钮文字 openPanel.prompt = “Select” // 3. 设置禁止选择文件 openPanel.canChooseFiles = true if tag==0||tag==2 { openPanel.canChooseFiles = false } // 4. 设置可以选择目录 openPanel.canChooseDirectories = true if tag==1 { openPanel.canChooseDirectories = false openPanel.allowedFileTypes=[“plist”] } // 5. 弹出面板框 openPanel.beginSheetModal(for: self.view.window!) { (result) in // 6. 选择确认按钮 if result == NSApplication.ModalResponse.OK { // 7. 获取选择的路径 let path=openPanel.urls[0].absoluteString.removingPercentEncoding! if tag==0 { self.projectPath.stringValue=path let array=path.components(separatedBy:"/") if array.count>1{ let name=array[array.count-2] print(array) print(name as Any) self.projectName.stringValue=name } }else if tag==1 { self.exportOptionsPath.stringValue=path }else{ self.ipaPath.stringValue=path } let names:[NSString]=[“projectPath”,“exportOptionsPath”,“ipaPath”] UserDefaults.standard.setValue(openPanel.url?.path, forKey: names[tag] as String) UserDefaults.standard.setValue(self.projectName.stringValue, forKey: “projectName”) UserDefaults.standard.synchronize() // self.savePath.stringValue = (openPanel.directoryURL?.path)!// // 8. 保存用户选择路径(为了可以在其他地方有权限访问这个路径,需要对用户选择的路径进行保存)// UserDefaults.standard.setValue(openPanel.url?.path, forKey: kSelectedFilePath)// UserDefaults.standard.synchronize() } // 9. 恢复按钮状态// sender.state = NSOffState } }3.start()通过str.replacingOccurrences(of: “file://”, with: “")将路径和sh里的路径替换通过DispatchQueue.global(qos: .default).async获取Concurrent Dispatch Queue并开启Process()在处理完的terminationHandler里回到主线程更新UI@IBAction func start(_ sender: Any) { guard projectPath.stringValue != "” else { self.logTextField.stringValue=“工程目录不能为空”; return } guard projectName.stringValue != "" else { self.logTextField.stringValue=“工程名不能为空”; return } guard exportOptionsPath.stringValue != "" else { self.logTextField.stringValue=“exportOptions不能为空 xcode生成ipa文件夹中包含”; return } guard ipaPath.stringValue != "" else { self.logTextField.stringValue=“输出ipa目录不能为空”; return } var str1=“abc” let str2=“abc” if str1==str2{ print(“same”) } //save let objs:[Any]=[projectPath,exportOptionsPath,ipaPath] let names:[NSString]=[“projectPath”,“exportOptionsPath”,“ipaPath”] for i in 0…2{ let obj=objs[i] as! NSTextField UserDefaults.standard.setValue(obj.stringValue, forKey: names[i] as String) } UserDefaults.standard.setValue(self.projectName.stringValue, forKey: “projectName”) UserDefaults.standard.setValue(self.debugRelease.selectedSegment, forKey: “debugRelease”) UserDefaults.standard.synchronize() // self.showInfoTextView.string=“abc”; if isLoadingRepo { self.logTextField.stringValue=“正在执行上一个任务”; return }// 如果正在执行,则返回 isLoadingRepo = true // 设置正在执行标记 let projectStr=self.projectPath.stringValue let nameStr=self.projectName.stringValue let plistStr=self.exportOptionsPath.stringValue let ipaStr=self.ipaPath.stringValue let returnData = Bundle.main.path(forResource: “package”, ofType: “sh”) let data = NSData.init(contentsOfFile: returnData!) var str = NSString(data:data! as Data, encoding: String.Encoding.utf8.rawValue)! as String if debugRelease.selectedSegment==0 { str = str.replacingOccurrences(of: “DEBUG_RELEASE”, with: “debug”) }else{ str = str.replacingOccurrences(of: “DEBUG_RELEASE”, with: “release”) } str = str.replacingOccurrences(of: “NAME_PROJECT”, with: nameStr) str = str.replacingOccurrences(of: “PATH_PROJECT”, with: projectStr) str = str.replacingOccurrences(of: “PATH_PLIST”, with: plistStr) str = str.replacingOccurrences(of: “PATH_IPA”, with: ipaStr) str = str.replacingOccurrences(of: “file://”, with: “”) print(“返回的数据:(str)”); self.logTextField.stringValue=“执行中。。。”; DispatchQueue.global(qos: .default).async { // str=“aaaabc”// str = str.replacingOccurrences(of: “ab”, with: “dd”) // print(self.projectPath.stringValue)// print(self.exportOptionsPath.stringValue)// print(self.ipaPath.stringValue) let task = Process() // 创建NSTask对象 // 设置task task.launchPath = “/bin/bash” // 执行路径(这里是需要执行命令的绝对路径) // 设置执行的具体命令 task.arguments = ["-c",str] task.terminationHandler = { proce in // 执行结束的闭包(回调) self.isLoadingRepo = false // 恢复执行标记 //5. 在主线程处理UI DispatchQueue.main.async(execute: { self.logTextField.stringValue=“执行完毕”; }) } self.captureStandardOutputAndRouteToTextView(task) task.launch() // 开启执行 task.waitUntilExit() // 阻塞直到执行完毕 } }4.captureStandardOutputAndRouteToTextView()对执行脚本的日志监控为了看到脚本报错或执行成功提示,使用Pipe()监控 NSPipe一般是两个线程之间进行通信使用的在osx 系统中 ,沙盒有个规则:在App运行期间通过NSOpenPanel用户手动打开的任意位置的文件,把这个这个路径保存下来,后面都是可以直接用这个路径继续访问文件,但当App退出后再次运行,这个路径默认是不可以访问的fileprivate func captureStandardOutputAndRouteToTextView(_ task:Process) { //1. 设置标准输出管道 outputPipe = Pipe() task.standardOutput = outputPipe //2. 在后台线程等待数据和通知 outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() //3. 接受到通知消息 observe=NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe.fileHandleForReading , queue: nil) { notification in //4. 获取管道数据 转为字符串 let output = self.outputPipe.fileHandleForReading.availableData let outputString = String(data: output, encoding: String.Encoding.utf8) ?? "" if outputString != “”{ //5. 在主线程处理UI DispatchQueue.main.async { if self.isLoadingRepo == false { let previousOutput = self.showInfoTextView.string let nextOutput = previousOutput + “\n” + outputString self.showInfoTextView.string = nextOutput // 滚动到可视位置 let range = NSRange(location:nextOutput.utf8CString.count,length:0) self.showInfoTextView.scrollRangeToVisible(range) if self.observe==nil { return } NotificationCenter.default.removeObserver(self.observe!) return }else{ let previousOutput = self.showInfoTextView.string var nextOutput = previousOutput + “\n” + outputString as String if nextOutput.count>5000 { nextOutput=String(nextOutput.suffix(1000)); } // 滚动到可视位置 let range = NSRange(location:nextOutput.utf8CString.count,length:0) self.showInfoTextView.scrollRangeToVisible(range) self.showInfoTextView.string = nextOutput } } } if self.isLoadingRepo == false { return } //6. 继续等待新数据和通知 self.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() } }-exportOptions.Plist 常用文件内容格式compileBitcodeFor non-App Store exports, should Xcode re-compile the app from bitcode? Defaults to YESembedOnDemandResourcesAssetPacksInBundleFor non-App Store exports, if the app uses On Demand Resources and this is YES, asset packs are embedded in the app bundle so that the app can be tested without a server to host asset packs. Defaults to YES unless onDemandResourcesAssetPacksBaseURL is specifiedmethodDescribes how Xcode should export the archive. Available options: app-store, ad-hoc, package, enterprise, development, and developer-id. The list of options varies based on the type of archive. Defaults to developmentteamIDThe Developer Portal team to use for this export. Defaults to the team used to build the archivethinningFor non-App Store exports, should Xcode thin the package for one or more device variants? Available options: <none> (Xcode produces a non-thinned universal app), <thin-for-all-variants> (Xcode produces a universal app and all available thinned variants), or a model identifier for a specific device (e.g. “iPhone7,1”). Defaults to <none>uploadBitcodeFor App Store exports, should the package include bitcode? Defaults to YESuploadSymbolsFor App Store exports, should the package include symbols? Defaults to YES ...

March 11, 2019 · 4 min · jiezi

为什么自己写的组件库被引用总是报错——详解webpack的library和libraryTarget

如果我们仅仅是实现一个项目,我们大概率不会关注到webpack output中的这两个属性。但是如果我们是实现一个组件库,那么这两个属性就变得至关重要了。本文从自己之前遇到的一个问题说起,继而引申出library和libraryTarget属性。1. 故事起源当我自己开始写第一个组件库的时候,很快我就撸好了框架的代码,然后我兴致冲冲的把我的组件库引入到我的项目中,我记得那时候我是这么写的:组件库:import Feeds from ‘@/components/feeds/index’;export { Feeds,};主项目:import Feeds from ‘@/tencent/newsH5Ad’;// 一些其他代码<Feeds data=‘xxx’>然后我就收获了一个报错,Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: null。啊?难道是我的最终输出代码有问题?我检查了一下最终输出的代码,没有问题,Feed组件的代码也在里面。这个问题我查了很久,都没有答案,最后才发现是webpack打包的问题。这就涉及到了本文的主角,library和libraryTarget。2.2. library和libraryTarget我们都知道,webpack可以将不同的模块化方式(commonjs, AMD, CMD, ES6 Module)的代码打包。那我们打出来的代码包其实也可以按不同的模块化方式生成,所以:libraryTarget就是配置webpack打包内容的模块方式的参数而library就是webpack打包内容的名字所以library规定了组件库返回值的名字,libraryTarget规定了返回值的编码格式。libraryTarget的配置选项可以分为四大类:2.1 按不同的模块方式生成也就是我们这个问题的解决方法,由于我写的是一个React的UI组件库,所以我们需要commonjs的模块方式。因此只需要在webpack.config.js中配置这一项即可:module.exports = { entry: ‘./src/index.js’, output: { filename: ‘index.js’, // library: ‘MyLibrary’, // 模块名称 libraryTarget: ‘commonjs2’, // 输出格式 }, // 其他代码}事实上,你可以选择的选项有:commonjs/commonjs2: 将你的library暴露为CommonJS模块amd: 将你的library暴露为amd模块umd: 将你的library暴露为所有的模块定义下都可运行的方式其中AMD和UMD需要指定library,如果不声明组件库则不能正常运行。这是为了在浏览器上通过script标签加载时,用AMD模块方式输出的组件库可以有明确的模块名。如:define(“MyLibrary”, [], function() { return entry_return; // 此模块返回值,是入口 chunk 返回的值});注意:commonjs和commonjs2几乎相同,只不过commonjs只包含exports,而commonjs2还包含module.exports,所以直接使用commonjs2即可。2.2 生成为一个变量libraryTarget的默认值是var,顾名思义,就是将组件库入口起点的返回值生成一个变量。如:var MyLibrary = entry_return;也可以选择‘assign’,那样的话将默认生成和一个全局的变量。不管是var还是assign,都需要设置library的名称,否则就会报错。2.3 生成一个为一个对象的属性和第二种情况差不多,只不过会把这个变量赋值给某个对象,作为它的一个属性存在。可以选择的选项有:this: 返回值成为this的一个属性window: 返回值成为window的一个属性global: 返回值成为global的一个属性例如:this[“MyLibrary”] = entry_return;window[“MyLibrary”] = entry_return;global[“MyLibrary”] = entry_return;可以看到,这种情况下也必须指定library的名字。2.4 异步生成方式在这种情况下,libraryTarget的值为‘jsonp’,组件库入口起点的返回值,会被包裹到一个jsonp包装容器中,并配合webpack的externals使用——组件库的依赖由externals指定。如:MyLibrary(entry_return);3. 总结本文介绍了webpack中libraray和libraryTarget的相关内容,解释了为什么不设置它们时使用webpack打包出来的组件库会有问题。一般情况下,作为vue或者react组件库,libraryTarget在commonjs2,amd,umd中三者择其一即可。参考文献《webpack文档》 ...

January 21, 2019 · 1 min · jiezi